Token lifecycle

Two things expire on different clocks: the API key you create in the UI lasts months, and the access token you exchange it for lasts an hour.

text
Create key (Settings → MCP)            ← one time
    │
    ▼
Exchange: POST /api/v1/oauth/token      ← every ~1 hour
    │     client_id + client_secret → JWT
    ▼
Use JWT for API / MCP requests          ← every request
    │     Authorization: Bearer <jwt>
    │
    ├─ Token expires (1 hour)
    │     → Exchange again with same credentials
    │
    ├─ Key revoked by admin
    │     → All requests fail immediately (401)
    │
    └─ Key expired (30–180 days from creation)
          → Exchange fails, create a new key

Access tokens#

PropertyValue
Lifetime1 hour (expires_in: 3600)
FormatSigned JWT
RefreshExchange the same client ID and secret again — there is no refresh token
ScopeFixed at exchange time; to change scopes, exchange a new token

Cache the token for its full hour instead of exchanging per request, or you'll hit the 60/min exchange limit. A practical pattern is to store the token with its expiry and re-exchange when fewer than ~5 minutes remain, so a long-running import doesn't fail mid-flight.

An expired token returns 401. Exchange a new one and retry the request.

API keys#

PropertyValue
Lifetime30–180 days from creation, depending on how the key was configured
SecretShown once at creation and never again
RevocationImmediate — in-flight tokens from that key stop working

When a key expires or is revoked, the token exchange fails with invalid_client rather than a distinct error, so treat that response as "create a new key" once you've ruled out a wrong secret. Rotate by creating the new key first, deploying it, then revoking the old one.

Presigned upload URLs#

Upload URLs handed back by the import endpoints are a third, much shorter clock: they expire 1 hour after they're issued, independently of your access token. If an upload URL expires before you use it:

  • CSV parts — call retry a part for a fresh URL.
  • Survey media files — call retry a media file.
  • Files and insights — call initialize again (files, insights). This creates a new wav_key or insight, since neither has a retry endpoint.