Skip to main content
Driftstack DRIFTSTACK

Concurrency & backpressure

Driftstack caps the number of concurrent sessions an account can keep open. This page covers the cap per tier, the 429 signal you get when you hit it, and how to back off cleanly from clients.

Concurrent-session caps

Caps below match TIER_CONCURRENT_SESSION_LIMITS in @driftstack/api-types; the server reads from the same constant. Numbers are deliberately conservative — Driftstack bills per-session, not per-call.

TierCapNotes
free1Perpetual free evaluation tier (1 session, 20-min cap).
solo_manual1Manual operator workflow.
team_manual3Shared across the team.
agency_manual8Shared across the agency.
api_starter2
api_builder8
api_scale24
enterprise32Contract path can raise this further.

"Concurrent session" = a session that has not yet been destroyed (status ≠ destroyed & ≠ errored). Cleanup is your responsibility — leaked sessions count against the cap until the 30-minute idle-cleanup sweep runs.

The 429 signal

When a POST /v1/sessions would push you past the cap, the server responds with:

HTTP/1.1 429 Too Many Requests
Retry-After: 15
Content-Type: application/problem+json

{
  "type": "https://errors.driftstack.dev/concurrency-limit",
  "title": "Concurrent session limit reached",
  "status": 429,
  "detail": "Account already has 20 active sessions; tier permits 20.",
  "current_sessions": 20,
  "limit": 20
}

The Retry-After header is a hint, not a contract — it's the time we estimate it'd take for one of your current sessions to naturally complete (based on your average session duration). Don't sleep blindly past it; respond when one of your own sessions finishes.

The canonical client backoff loop

Switch on the RFC 7807 type URI — clients should dispatch on the stable problem-type, not on title or detail strings:

async function createSessionWithBackoff(client, opts) {
  const maxAttempts = 5;
  let delay = 1_000;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await client.sessions.create(opts);
    } catch (err) {
      if (err.type !== 'https://errors.driftstack.dev/concurrency-limit') throw err;
      const retryAfter = (err.retryAfterSeconds ?? delay / 1000) * 1000;
      await sleep(retryAfter);
      delay = Math.min(delay * 2, 60_000);   // exponential cap at 60s
    }
  }
  throw new Error('Could not acquire a session slot after retries');
}

Don't pre-emptively rate-limit yourself client-side. The server's cap is the truth — back off only when it tells you to.

Pooling sessions vs creating ephemeral ones

Two patterns work well:

For pooling: don't oversubscribe. If your tier caps at 20, run a pool of 18 — leaves headroom for ad-hoc creates that shouldn't have to evict pooled sessions.

Rate limits vs. concurrency limits

These are separate systems:

Both surface as 429s but with different type URIs (.../rate-limited vs. .../concurrency-limit) so clients can dispatch on the stable problem-type.

Raising the cap

Upgrade to a higher tier via /pricing — the new cap applies immediately on tier change. For Enterprise overrides above the default 500, email [email protected]; we can lift the cap without a tier change for short-term campaign bursts.

Observability

Track your own concurrency from the dashboard or via GET /v1/account/me (the response includes concurrent_session_active alongside concurrent_session_cap). For per-day patterns, the audit log carries session.created + session.destroyed entries — diff them for an open-session timeseries.

Support

Capacity questions or unexpected 429s: [email protected].