Skip to content
Get started

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.

  1. An organization admin creates one organization API key with the Manage members, project access and API keys scope, once, in the web app.
  2. Your portal stores that key server-side and uses it to call the API.
  3. 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.
  4. 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.
  5. 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:

  1. In app.scenario.com, open Settings, then API Keys under Organization, and click Add API Key.
  2. Choose the Organization type.
  3. Turn on the scope Manage members, project access and API keys (team.members.manage in the API).
  4. 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:

Terminal window
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.

Project keys belong to one project. List the organization’s projects with GET /v1/projects:

Terminal window
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.

Call POST /v1/projects/{projectId}/api-keys with the organization key:

Terminal window
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.

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) or max_retries=0 (Python), as shown below.
  • When the outcome is unknown, list the organization’s keys and look for the user’s key name before creating it again.

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.

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:

Terminal window
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.

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:

Terminal window
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.

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 with 403.
  • Change a key’s cap later: PUT /v1/teams/{teamId}/members/{memberId} with the key’s userId as memberId and { "maxConsumption": <n> }: -1 for uncapped, null to 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_...) as memberId, not its id. 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.
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.