Skip to main content

API Key Scopes & Workspace Binding

Every DraftLift API key carries two governance fields beyond its name:
  1. Scopes — what the key can do (read, write).
  2. Workspace binding — which workspace the key operates against (a specific workspace, or all of them).
Both are chosen when you create the key in Settings → API Keys and cannot be changed after issue. Re-issue the key if you need different settings.

Scopes

ScopeWhat it allows
readList, get, search — every GET endpoint.
writeEverything read allows, plus create, update, delete, and generate.
Scopes are additive. A key with ["read", "write"] is the most permissive issuable today; a read-only key is read-only across the entire API. The admin scope is reserved in the schema but is not surfaced in the UI. v1 keys cannot manage other API keys, billing, or admin endpoints — those operations always require an interactive web session and are denylisted on the public API. See Authentication for the full denylist.

When to issue a read-only key

Use read-only keys for:
  • Reporting dashboards, analytics jobs, or anything that only consumes existing content.
  • Long-lived background services that should never mutate state if they get compromised.
  • Sharing data with third-party tooling.

When to include write

Use read + write keys for:
  • AI agents that generate, edit, score, or schedule content.
  • CI/CD pipelines that publish or convert drafts.
  • The CLI on a developer laptop.
The default in the create dialog is read + write because most CLI/MCP usage requires both. Untick Write to harden a key meant for read-only consumers.

Insufficient scope errors

A scope-gated mutation called with a read-only key returns:
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "FORBIDDEN",
  "message": "This endpoint requires the 'write' scope.",
  "details": {
    "error_code": "insufficient_scope",
    "required_scope": "write",
    "current_scopes": ["read"],
    "upgrade_action": "Re-issue your API key with the required scope at https://app.draftliftai.com/settings/api-keys"
  },
  "trace_id": "tr_..."
}
The CLI maps this to a typed InsufficientScopeError with exit code 2 and prints the upgrade_action URL on its own line.

Workspace binding

Workspaces partition references, memories, content calendars, and generated drafts. A workspace is identified by its UUID (ws_...). When you create a key, you choose one of two postures:
PostureWhen to use
All workspacesMulti-workspace agents, CLI on a developer laptop, anything that needs to operate across workspaces.
Bound to a workspacePer-client keys, scoped CI tokens, or any key you want to silently lock to one workspace’s data.
If you have exactly one workspace, the create dialog hides the workspace picker and binds the key to that workspace automatically.

Behavior at request time

RequestOutcome
Unbound key, no workspace_idServer resolves nothing; endpoints that require a workspace return 400 (or operate on the user’s default, depending on the route).
Unbound key, explicit workspace_idOperates on the requested workspace.
Bound key, no workspace_idServer injects the bound workspace automatically (200 OK).
Bound key, workspace_id matches the binding200 OK.
Bound key, workspace_id differs from the binding403 workspace_mismatch. The server never silently overrides.

Workspace mismatch errors

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "FORBIDDEN",
  "message": "This API key is bound to a specific workspace.",
  "details": {
    "error_code": "workspace_mismatch",
    "bound_workspace_id": "ws_aaa...",
    "requested_workspace_id": "ws_bbb..."
  },
  "trace_id": "tr_..."
}
The CLI surfaces both IDs in a WorkspaceMismatchError (exit code 2) and suggests either re-issuing the key without a binding or running with --workspace <bound>.

Choosing settings for common scenarios

ScenarioScopesWorkspace binding
Agent producing content for one clientread + writeBound to that client’s workspace
Internal CI that converts blog → social across all brandsread + writeUnbound
Read-only metrics dashboardreadUnbound (or bound, if scoped)
Personal CLI on a laptopread + writeUnbound
Third-party integration that only fetches contentreadBound to a “public” workspace

Rotation and expiry

When you create a key, pick an expiration: 30 days, 90 days (default), 365 days, or Never. The default is 90 days because it forces a yearly-quarterly rotation cadence without being onerous; pick Never only for keys you actively monitor in audit logs. To rotate:
  1. Create a new key with the same scopes and binding.
  2. Roll the key into your config or secret store.
  3. Revoke the old key from Settings → API Keys.
Revocation takes effect immediately — in-flight requests using the old key will start receiving 401 Unauthorized within seconds.

Introspecting a key

You can check what a key can do at any time:
dl whoami --json
Or call the introspection endpoint directly:
curl -H "Authorization: Bearer dl_live_..." \
  https://draftliftai.com/api/v1/api-keys/current
This returns the calling key’s name, scopes, workspace_id, expires_at, and last_used_at. It is the only /api-keys/* endpoint that accepts dl_live_ keys — every other key-management operation requires a web session.

Next steps

CLI Quickstart

Walk through dl login, references, and your first generation.

Authentication

Full reference for keys, headers, error shapes, and the denylist.