Provisioning Per-User API Keys
Let your own portal create a Scenario project API key for each user with one organization API key, and revoke it when the user leaves.
If your studio runs its own internal portal, access-request tool or RBAC in front of Scenario, you don’t have to create API keys by hand in the web app. One organization API key, held by your portal’s backend, can create a project API key for each user through the API, and revoke it when that user leaves or changes team.
This guide covers the one-time setup, how to create, hand out, list and revoke per-user keys, and the guardrails Scenario applies to them.
How it works
Section titled “How it works”- An organization admin creates one organization API key with the Manage members, project access and API keys scope, once, in the web app.
- Your portal stores that key server-side and uses it to call the API.
- When a user needs access, your portal creates a project API key for them on the right project, with a role and an optional usage cap.
- Scenario returns the new key’s secret once. Your portal hands it to the user, or keeps it and calls Scenario on the user’s behalf.
- When the user leaves, your portal revokes their key.
| Key | Held by | Can do |
|---|---|---|
| Organization key | Your portal’s backend only | Create and revoke project API keys, manage project members, set member and project usage caps, across the organization |
| Project key | One user (or your portal) | Work in its one project, with the role it was given |
Per-user keys give you per-user attribution (each key’s usage, last use and activity are tracked separately) and per-key usage caps. They do not raise rate limits or throughput.
Before you start: create the organization key
Section titled “Before you start: create the organization key”An organization admin does this once:
- In app.scenario.com, open Settings, then API Keys under Organization, and click Add API Key.
- Choose the Organization type.
- Turn on the scope Manage members, project access and API keys (
team.members.managein the API). - Save, and copy the key ID and secret into your secret manager.
With this scope, the key can invite and remove members, set their project roles, create and revoke project API keys, and set member and project usage limits, including unlimited. It cannot make anyone an organization admin or a project owner, and cannot change an admin.
All requests below go to https://api.cloud.scenario.com/v1 with Basic authentication, using the organization key. The examples read it from two environment variables:
export ORG_KEY_ID="your-organization-key-id"export ORG_KEY_SECRET="your-organization-key-secret"AUTH="Authorization: Basic $(printf '%s:%s' "$ORG_KEY_ID" "$ORG_KEY_SECRET" | base64 | tr -d '\n')"tr -d '\n' matters on Linux, where base64 wraps its output at 76 characters and would split the header.
Find your project IDs
Section titled “Find your project IDs”Project keys belong to one project. List the organization’s projects with GET /v1/projects:
curl "https://api.cloud.scenario.com/v1/projects" -H "$AUTH"With the organization key, the response lists every project of the organization:
{ "projects": [ { "id": "proj_k9Xq2mWfPz7LrT4vYbN8cD1e", "name": "Starforge Concept Art", "teamId": "vQ3mT8pLx2RwN6yHb4kZcA" } ]}Keep the teamId too: you need it to list keys.
Create a key for a user
Section titled “Create a key for a user”Call POST /v1/projects/{projectId}/api-keys with the organization key:
curl -X POST "https://api.cloud.scenario.com/v1/projects/proj_k9Xq2mWfPz7LrT4vYbN8cD1e/api-keys" \ -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "maya.chen@starforge.games", "role": "editor", "maxConsumption": 5000 }'| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | A label for the key, up to 256 characters. Use something that identifies the user, such as their email, so you can find the key again. |
role |
string | No | The key’s project role: editor (the default) or reader, or a custom ;-separated scope list such as assets.read;workflows.run. admin is refused for keys created by an organization key. |
maxConsumption |
number | No | Usage cap for this key over the billing period, in Creative Units: a value of 0 or more caps it, -1 leaves it uncapped. Omit it to inherit the organization’s default. |
The response contains the new key and its secret:
{ "id": "api_V1stGXR8Z5jdHi6BmyT3aB2c", "secret": "s3cr3tExampleValue000000", "authorId": "apiu_4E63d6989DcD19bBe05AcBa8", "userId": "apiu_0A5C6BfCa3dE4Ca8aD77dAcF", "ownerId": "proj_k9Xq2mWfPz7LrT4vYbN8cD1e", "ownerType": "project", "teamId": "vQ3mT8pLx2RwN6yHb4kZcA", "createdAt": "2026-10-13T16:10:06.280Z"}| Field | Description |
|---|---|
id |
The new key’s ID, which the user sends as the Basic auth username. You also need it to revoke the key. |
secret |
The new key’s secret, the Basic auth password. |
userId |
The new key’s member ID, which the project member routes take. |
authorId |
The member ID of the organization key that created it. |
ownerId |
The project the key was created on, which the revoke route takes. |
teamId |
The organization the key belongs to, which the list route takes. |
Store at least the key id, its ownerId (project) and its userId against the user in your portal, so you can revoke or adjust it later.
The new key only sees its own project: GET /v1/projects called with it returns that single project. Keys created through the API also show up in the web app’s API Keys page and in the activity trail, like keys created by hand.
Don’t retry key creation automatically
Section titled “Don’t retry key creation automatically”If creation fails after the key was stored, Scenario revokes the half-created key and answers with the header x-should-retry: false. Never retry this call blindly: if a timeout or a dropped connection hides a successful response, a retry creates a second key.
- With your own HTTP client, turn automatic retries off for this request.
- With the SDKs, which retry failed requests by default, pass
maxRetries: 0(TypeScript) ormax_retries=0(Python), as shown below. - When the outcome is unknown, list the organization’s keys and look for the user’s key
namebefore creating it again.
With the SDKs
Section titled “With the SDKs”projects.apiKeys (projects.api_keys in Python) is available from TypeScript SDK 3.3.0 and Python SDK 2.3.0.
import Scenario from '@scenario-labs/sdk';
const client = new Scenario({ apiKey: process.env.ORG_KEY_ID, apiSecret: process.env.ORG_KEY_SECRET,});
const key = await client.projects.apiKeys.create( 'proj_k9Xq2mWfPz7LrT4vYbN8cD1e', { name: 'maya.chen@starforge.games', role: 'editor', maxConsumption: 5000 }, { maxRetries: 0 },);
// key.id and key.secret are the user's credentials; the secret is never shown again.import osfrom scenario_sdk import Scenario
client = Scenario( api_key=os.environ["ORG_KEY_ID"], api_secret=os.environ["ORG_KEY_SECRET"],)
key = client.with_options(max_retries=0).projects.api_keys.create( "proj_k9Xq2mWfPz7LrT4vYbN8cD1e", name="maya.chen@starforge.games", role="editor", max_consumption=5000,)
# key.id and key.secret are the user's credentials; the secret is never shown again.Revoke a key
Section titled “Revoke a key”When a user leaves, call DELETE /v1/projects/{projectId}/api-keys/{keyId} with the organization key, the project the key was created on (its ownerId) and the key’s id:
curl -X DELETE "https://api.cloud.scenario.com/v1/projects/proj_k9Xq2mWfPz7LrT4vYbN8cD1e/api-keys/api_V1stGXR8Z5jdHi6BmyT3aB2c" \ -H "$AUTH"The response is 200 with an empty object, {}. From then on, any request made with the revoked key is refused with 403:
{ "reason": "Given APIKey does not exists" }With the SDKs: client.projects.apiKeys.delete(keyId, { projectId }) in TypeScript, client.projects.api_keys.delete(key_id, project_id=...) in Python.
An organization key cannot revoke a project admin’s key, a key that holds team.members.manage, or another organization key.
List keys and reconcile
Section titled “List keys and reconcile”To audit keys, or to check what exists after an uncertain create, call GET /v1/teams/{teamId}. The response’s team.keys lists every API key of the organization:
curl "https://api.cloud.scenario.com/v1/teams/vQ3mT8pLx2RwN6yHb4kZcA" -H "$AUTH"{ "team": { "id": "vQ3mT8pLx2RwN6yHb4kZcA", "keys": [ { "id": "apiu_0A5C6BfCa3dE4Ca8aD77dAcF", "apiKeyId": "api_V1stGXR8Z5jdHi6BmyT3aB2c", "name": "maya.chen@starforge.games", "apiKeyStatus": "active", "scope": "project", "ownerId": "proj_k9Xq2mWfPz7LrT4vYbN8cD1e", "authorId": "apiu_4E63d6989DcD19bBe05AcBa8", "projects": [ { "projectId": "proj_k9Xq2mWfPz7LrT4vYbN8cD1e", "role": "editor" } ], "consumption": 1240, "maxConsumption": 5000, "lastUsedAt": "2026-10-13T17:02:51.114Z", "createdAt": "2026-10-13T16:10:06.280Z", "updatedAt": "2026-10-13T16:10:06.280Z" } ] }}| Field | Description |
|---|---|
id |
The key’s member ID (apiu_...), the userId returned at creation. |
apiKeyId |
The key’s ID (api_...), the one you pass to revoke it. |
scope |
project for project keys, team for organization keys. |
projects |
For a project key, the projects it has access to and its role on each. Empty once it has none. |
ownerId |
For a project key, the project it was created on. |
consumption |
Creative Units used by the key over the current billing period. |
maxConsumption |
The key’s effective cap for the period. Absent when the key is uncapped. |
lastUsedAt |
When the key last authenticated a request. Absent if it has never been used. |
A periodic job that compares this list with your portal’s records catches keys that should no longer exist, such as keys of users who left while your portal was down.
Other things the organization key can do
Section titled “Other things the organization key can do”The same scope covers a few related operations, documented in the API Reference:
- Cap a project’s usage:
PUT /v1/projects/{projectId}with a body of only{ "maxConsumption": <n> }. Any other field in the body is refused with403. - Change a key’s cap later:
PUT /v1/teams/{teamId}/members/{memberId}with the key’suserIdasmemberIdand{ "maxConsumption": <n> }:-1for uncapped,nullto fall back to the organization’s default. - Manage project members: add, update and remove members of a project, with any role except
admin. Adding works for people only, not for API keys. - Change a key’s role or remove it from its project: the update and remove routes above, with the key’s
userId(apiu_...) asmemberId, not itsid. A key’s role can only be changed on the project it was created on, and removing a key from its last project revokes it.
Limits and errors
Section titled “Limits and errors”| Situation | Result |
|---|---|
role is admin |
403 {"reason":"Team API keys cannot grant project admin privileges"} |
role is not a known role or scope list |
400 |
name is longer than 256 characters |
400 |
| The organization reached its plan’s API key limit | 429. The limit depends on the plan. On plans with unlimited keys, an organization key can create up to 1,000. |
| Revoking a key on a project other than the one it was created on | 404 |
| The call is made with a project key, or without the required scope | 403 |
Revoking a project admin’s key, or a key with team.members.manage |
403 |
| A request made with a revoked key | 403 {"reason":"Given APIKey does not exists"} |
| Creation failed after the key was stored | An error with x-should-retry: false; the key was revoked. Don’t retry blindly. |
For the general shape of error responses, see Understanding API Responses and Errors.