|
| 1 | +# Roboflow API Key Management |
| 2 | + |
| 3 | +> **Source-of-truth note:** This page ships with the Roboflow plugin. If your client has the plugin loaded, prefer the local skill (`roboflow:api-reference`) over fetching `roboflow://skills/api-reference/api-key-management` via `ReadMcpResourceTool` — the MCP resources are a fallback for non-plugin clients and may lag the source repo. |
| 4 | +
|
| 5 | +> **Tip:** If you're connected to the [Roboflow MCP server](https://mcp.roboflow.com), prefer its `api_keys_*` tools over raw REST calls — they handle auth and typed responses for you. The REST patterns below stay relevant if you're not using MCP. |
| 6 | +
|
| 7 | +Base URLs: |
| 8 | +- Production: `https://api.roboflow.com` |
| 9 | +- Staging: `https://api.roboflow.one` |
| 10 | + |
| 11 | +**Auth for management endpoints:** pass the API key via `?api_key=` query parameter or `Authorization: Bearer` header. The workspace publishable key (`rf_<workspaceId>`) is NOT accepted — you must use a private API key (or a scoped key that includes `api-key:read` / `api-key:create` / etc.). |
| 12 | + |
| 13 | +Keys are addressed by their non-secret `keyId` field. Secret key values are **never returned** by any list or get endpoint — they are shown exactly once, in the 201 response from the create endpoint. |
| 14 | + |
| 15 | +## Publishable vs. Private Keys |
| 16 | + |
| 17 | +| | Publishable Key | Private API Key | |
| 18 | +|---|---|---| |
| 19 | +| Format | `rf_<workspaceId>` | opaque secret string | |
| 20 | +| Secret? | No — safe to expose in client-side / browser code | Yes — treat like a password | |
| 21 | +| Retrieval | `GET /:workspace/api-keys/publishable` | Created via POST; secret shown once | |
| 22 | +| Lifecycle | Fixed per workspace; cannot be created or revoked | Can be created, disabled, and revoked | |
| 23 | +| Capabilities | Inference and model download on workspace models only | Full API access (scoped by the key's own scopes) | |
| 24 | +| Typical use | Browser/edge inference via `roboflow.js` / inferencejs | Server-side automation, CI/CD, MCP tools | |
| 25 | + |
| 26 | +The publishable key grants inference and model-download access on that workspace's models to **anyone who holds it** — treat it accordingly (it's not a secret, but scope it to what you actually publish). Use a scoped private key for any server-side operation. |
| 27 | + |
| 28 | +## REST Endpoints |
| 29 | + |
| 30 | +### List API Keys |
| 31 | + |
| 32 | +``` |
| 33 | +GET /:workspace/api-keys |
| 34 | +``` |
| 35 | + |
| 36 | +Query params: `includeDisabled` (bool), `includeFolders` (bool) |
| 37 | + |
| 38 | +Returns all non-secret key metadata for the workspace. The secret value is never included. |
| 39 | + |
| 40 | +```bash |
| 41 | +curl "https://api.roboflow.com/my-workspace/api-keys?api_key=KEY" |
| 42 | +``` |
| 43 | + |
| 44 | +Response: |
| 45 | + |
| 46 | +```json |
| 47 | +{ |
| 48 | + "apiKeys": [ |
| 49 | + { |
| 50 | + "keyId": "abc123", |
| 51 | + "name": "CI pipeline", |
| 52 | + "prefix": "rf_ci_", |
| 53 | + "scopes": ["image:read", "image:create"], |
| 54 | + "folderIds": [], |
| 55 | + "default": false, |
| 56 | + "protected": true, |
| 57 | + "disabled": false, |
| 58 | + "created_on": "2024-01-15T10:30:00Z", |
| 59 | + "created_by": "user@example.com", |
| 60 | + "customMetadata": {} |
| 61 | + } |
| 62 | + ], |
| 63 | + "publishableKey": "rf_myworkspaceid" |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +### Get a Single Key |
| 68 | + |
| 69 | +``` |
| 70 | +GET /:workspace/api-keys/:keyId |
| 71 | +``` |
| 72 | + |
| 73 | +```bash |
| 74 | +curl "https://api.roboflow.com/my-workspace/api-keys/abc123?api_key=KEY" |
| 75 | +``` |
| 76 | + |
| 77 | +Response: `{ "apiKey": { ...same fields as above... } }` |
| 78 | + |
| 79 | +### Get Publishable Key |
| 80 | + |
| 81 | +``` |
| 82 | +GET /:workspace/api-keys/publishable |
| 83 | +``` |
| 84 | + |
| 85 | +```bash |
| 86 | +curl "https://api.roboflow.com/my-workspace/api-keys/publishable?api_key=KEY" |
| 87 | +``` |
| 88 | + |
| 89 | +Response: `{ "publishableKey": "rf_myworkspaceid" }` |
| 90 | + |
| 91 | +### Create a Key |
| 92 | + |
| 93 | +``` |
| 94 | +POST /:workspace/api-keys |
| 95 | +``` |
| 96 | + |
| 97 | +Body fields (all optional): |
| 98 | + |
| 99 | +| Field | Type | Description | |
| 100 | +|-------|------|-------------| |
| 101 | +| `name` | string | Human-readable label | |
| 102 | +| `scopes` | string[] or null | Scope strings to grant (or `role:<name>` presets). Omit to inherit the calling credential's scopes; `null` for full access (if the caller has it); `[]` for no abilities | |
| 103 | +| `folderIds` | string[] | Restrict key to specific folders (requires Advanced API Keys) | |
| 104 | +| `customMetadata` | object | Arbitrary key-value metadata | |
| 105 | +| `protected` | bool | Mark as protected (cannot be disabled/revoked without dashboard action) | |
| 106 | + |
| 107 | +`scopes`, `folderIds`, and `customMetadata` require the **Advanced API Keys** plan feature. A new key can never exceed the caller's own abilities. |
| 108 | + |
| 109 | +**Who can create keys.** The acting credential may create keys only if it is **unscoped** (full access) or was granted the `api-key:create` scope; an OAuth token also needs the `create_api_key` permission. On workspaces with **Advanced API Keys**, new keys created from the dashboard are **scoped** (not full access) and `api-key:create` is **off by default** — include it explicitly only if the key must manage keys. |
| 110 | + |
| 111 | +**Scopes are stored as explicit leaves.** A section name (e.g. `model`) or a `role:<name>` preset is expanded to its current leaf scopes at create time, so a key never auto-gains an ability added to that group later — request the new scope explicitly when you need it. |
| 112 | + |
| 113 | +**Agents: scope to least privilege.** When you create a key on a workspace that has **Advanced API Keys**, you should pass an explicit `scopes` array covering only the abilities the key actually needs (e.g. `["image:create"]` for an upload-only key) rather than a full-access key. If the workspace does **not** have Advanced API Keys, only full-access keys can be created — tell the user that scoped, least-privilege keys are available on the **Enterprise** plan. |
| 114 | + |
| 115 | +**A workspace-wide key** (no `folderIds`) requires the caller to have access to **all** folders in the workspace; otherwise scope the key to specific `folderIds` you can access. |
| 116 | + |
| 117 | +```bash |
| 118 | +curl -X POST "https://api.roboflow.com/my-workspace/api-keys?api_key=KEY" \ |
| 119 | + -H "Content-Type: application/json" \ |
| 120 | + -d '{"name": "read-only ingest", "scopes": ["image:read"]}' |
| 121 | +``` |
| 122 | + |
| 123 | +Response (201): |
| 124 | + |
| 125 | +```json |
| 126 | +{ |
| 127 | + "keyId": "abc123", |
| 128 | + "key": "rf_ACTUAL_SECRET_VALUE", |
| 129 | + "name": "read-only ingest", |
| 130 | + "scopes": ["image:read"], |
| 131 | + "folderIds": [], |
| 132 | + "protected": false, |
| 133 | + "publishableKey": "rf_myworkspaceid" |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +**The `key` field in this response is the only time the secret is ever exposed.** Store it immediately (e.g. in a secrets manager); it cannot be retrieved again. |
| 138 | + |
| 139 | +### Update a Key |
| 140 | + |
| 141 | +``` |
| 142 | +PATCH /:workspace/api-keys/:keyId |
| 143 | +``` |
| 144 | + |
| 145 | +Body fields (all optional — send only the fields you want to change): |
| 146 | + |
| 147 | +| Field | Type | Notes | |
| 148 | +|-------|------|-------| |
| 149 | +| `name` | string | Rename the key | |
| 150 | +| `scopes` | `string[] \| null` | Replace the key's scopes (tri-state — see below). Requires Advanced API Keys | |
| 151 | +| `customMetadata` | object | Replace the key's metadata. Requires Advanced API Keys | |
| 152 | +| `protected` | bool | Set to `true` only — unprotect is dashboard-only | |
| 153 | +| `disabled` | bool | Temporarily disable or re-enable. Requires Advanced API Keys | |
| 154 | + |
| 155 | +**The three states of `scopes`** (PATCH replaces, so omitting the field leaves scopes unchanged): |
| 156 | + |
| 157 | +- **Omitted** - the key's existing scopes are left unchanged. |
| 158 | +- **`null`** - the key becomes **full access** (unscoped). Use this to widen a scoped key back to full access; the caller must itself hold full access to grant it. |
| 159 | +- **`[]`** (empty array) - the key stays a valid credential but has **no abilities**. |
| 160 | +- **`["model:infer", ...]`** - **replaces** the scopes with exactly this set (a section name like `model` expands to all of that section's scopes). |
| 161 | + |
| 162 | +Sending `scopes` (including `[]` or `null`), `customMetadata`, or `disabled` requires the **Advanced API Keys** plan feature. |
| 163 | + |
| 164 | +```bash |
| 165 | +curl -X PATCH "https://api.roboflow.com/my-workspace/api-keys/abc123?api_key=KEY" \ |
| 166 | + -H "Content-Type: application/json" \ |
| 167 | + -d '{"name": "renamed key", "disabled": true}' |
| 168 | +``` |
| 169 | + |
| 170 | +Response: `{ "apiKey": { ...updated fields... } }` |
| 171 | + |
| 172 | +Note: `protected: true` can be set via API, but removing the protected flag requires the dashboard. Attempting to disable or revoke a protected key returns 409. |
| 173 | + |
| 174 | +### Revoke a Key |
| 175 | + |
| 176 | +``` |
| 177 | +DELETE /:workspace/api-keys/:keyId |
| 178 | +``` |
| 179 | + |
| 180 | +```bash |
| 181 | +curl -X DELETE "https://api.roboflow.com/my-workspace/api-keys/abc123?api_key=KEY" |
| 182 | +``` |
| 183 | + |
| 184 | +Response: `{ "status": "revoked", "keyId": "abc123" }` |
| 185 | + |
| 186 | +Revocation is permanent. If you might need to re-enable the key later, use `PATCH` with `disabled: true` instead. |
| 187 | + |
| 188 | +## OAuth Scopes for Key Management |
| 189 | + |
| 190 | +When using a scoped key or OAuth token to manage other keys, the caller's token needs one or more of these scopes: |
| 191 | + |
| 192 | +| Scope | Grants | |
| 193 | +|-------|--------| |
| 194 | +| `api-key:read` | List and get key metadata | |
| 195 | +| `api-key:create` | Create new keys | |
| 196 | +| `api-key:update` | Rename, scope, disable keys | |
| 197 | +| `api-key:revoke` | Delete keys permanently | |
| 198 | + |
| 199 | +## MCP Tools |
| 200 | + |
| 201 | +If you're using the Roboflow MCP server, prefer these tools over raw REST calls: |
| 202 | + |
| 203 | +| Tool | Description | |
| 204 | +|------|-------------| |
| 205 | +| `api_keys_list` | List all key metadata for a workspace | |
| 206 | +| `api_keys_get` | Get a single key by `keyId` | |
| 207 | +| `api_keys_get_publishable` | Get the workspace publishable key | |
| 208 | +| `api_keys_create` | Create a key (returns the one-time secret) | |
| 209 | +| `api_keys_update` | Rename, re-scope, or update metadata | |
| 210 | +| `api_keys_protect` | Mark a key as protected (no unprotect tool by design) | |
| 211 | +| `api_keys_disable` | Temporarily disable a key (reversible) | |
| 212 | +| `api_keys_revoke` | Permanently revoke a key | |
| 213 | + |
| 214 | +There is intentionally no `api_keys_unprotect` tool — removing the protected flag requires a deliberate dashboard action to prevent accidental exposure. |
| 215 | + |
| 216 | +## Python CLI |
| 217 | + |
| 218 | +The `roboflow` Python package exposes an `api-key` subcommand: |
| 219 | + |
| 220 | +```bash |
| 221 | +# List all keys |
| 222 | +roboflow api-key list |
| 223 | + |
| 224 | +# Get a specific key's metadata |
| 225 | +roboflow api-key get KEY_ID |
| 226 | + |
| 227 | +# Create a key (capture the secret immediately) |
| 228 | +roboflow --json api-key create "my-key-name" | jq -r .key |
| 229 | + |
| 230 | +# Create a scoped key |
| 231 | +roboflow --json api-key create "ci-read-only" --scope image:read --scope model:infer | jq -r .key |
| 232 | + |
| 233 | +# Rename a key |
| 234 | +roboflow api-key update KEY_ID --name "new name" |
| 235 | + |
| 236 | +# Disable a key (reversible) |
| 237 | +roboflow api-key disable KEY_ID |
| 238 | + |
| 239 | +# Mark a key as protected |
| 240 | +roboflow api-key protect KEY_ID |
| 241 | + |
| 242 | +# Get the workspace publishable key |
| 243 | +roboflow api-key publishable |
| 244 | + |
| 245 | +# Permanently revoke a key |
| 246 | +roboflow api-key revoke KEY_ID |
| 247 | +``` |
| 248 | + |
| 249 | +## Best Practices |
| 250 | + |
| 251 | +### Least-privilege scoping |
| 252 | + |
| 253 | +Only grant the scopes a key actually needs. A key used exclusively to upload images should have `image:create` — not a full unrestricted key. This limits the blast radius if the key is compromised. |
| 254 | + |
| 255 | +```bash |
| 256 | +# Good: scoped to what CI actually needs |
| 257 | +roboflow --json api-key create "github-actions-upload" \ |
| 258 | + --scope image:create --scope image:annotate | jq -r .key |
| 259 | +``` |
| 260 | + |
| 261 | +### Protect production keys |
| 262 | + |
| 263 | +Mark any key used in production workloads as `protected` immediately after creation. Protected keys cannot be accidentally disabled or revoked via API — only via a deliberate dashboard action. |
| 264 | + |
| 265 | +```bash |
| 266 | +# Protect a key after creating it |
| 267 | +roboflow api-key protect KEY_ID |
| 268 | +``` |
| 269 | + |
| 270 | +### Never commit secrets |
| 271 | + |
| 272 | +Store the API key in a `.gitignore`'d `.env` file or a secrets manager. The key value is returned only once (at creation time) — if you miss it, you must create a new key. |
| 273 | + |
| 274 | +```bash |
| 275 | +# .env (never commit this file) |
| 276 | +ROBOFLOW_API_KEY=rf_... |
| 277 | +``` |
| 278 | + |
| 279 | +### Rotate keys safely |
| 280 | + |
| 281 | +1. Create a new key with the same scopes as the key being rotated. |
| 282 | +2. Deploy the new key to all consumers. |
| 283 | +3. Verify the new key is working. |
| 284 | +4. Revoke the old key. |
| 285 | + |
| 286 | +This zero-downtime rotation avoids service interruptions. |
| 287 | + |
| 288 | +### Prefer disable over revoke when unsure |
| 289 | + |
| 290 | +`PATCH { "disabled": true }` is reversible. `DELETE` is permanent. If you're not certain a key is safe to remove (e.g. you're not sure all consumers have been updated), disable first and revoke after confirming. |
| 291 | + |
| 292 | +### The secret is shown exactly once |
| 293 | + |
| 294 | +The `key` field in the create response is the only time the plaintext secret appears. Copy it to your secrets manager immediately. There is no "show secret again" endpoint — if you lose it, create a new key and revoke the old one. |
0 commit comments