Opal limits each API key to 100 requests per 60-second window. Over the limit you get 429 with a Retry-After header. Plan, size, and count limits return 403, 413, or 400 with fields that say what was exceeded. Call GET /account before bulk work to stay inside your plan.
Rate Limits
| Limit | Applies to | When exceeded |
|---|---|---|
| 100 requests per key per 60-second window | Every API-key request | 429 with {"error":"Rate limit exceeded"} and a Retry-After header (seconds until the window resets). |
| 100 failed authentications per IP per 60-second window | Requests with a missing, malformed, unknown, or revoked key | 429 with {"error":"Rate limit exceeded"} and Retry-After. Until the window resets, every request from that IP gets 429, including requests with a valid key. |
| 20 share creations per user per 60-second window | POST /shares | 429 with Retry-After and retryAfterSeconds in the body. |
| 20 publish operations per user per 60-second window | POST /items/:id/publish, POST /items/:id/publish/refresh, DELETE /items/:id/publish | 429 with Retry-After and retryAfterSeconds in the body. Publishing in the app counts toward the same limit. |
Windows are fixed, not rolling: a window starts with the first request and resets 60 seconds later. The share and publish limits are separate counters, applied on top of the per-key limit. Use POST /items/batch/move to reposition up to 500 items in one request. See Position Items: Batch Move, Inbox, and Z-Order.
Handle a 429
Wait for the number of seconds in the Retry-After header, then try again. For share and publish limits, the response body also has retryAfterSeconds. An export 429 is different: it means you already exported everything in the last 24 hours, and it has no Retry-After header. See Export API: Download Your Workspaces as a Zip.
Limit Errors
Call GET /account before bulk work (many creates, uploads, or accepts) to stay inside these limits. See Plan, Limits, and Usage in the Opal API: GET /account.
| Limit | Routes | Response |
|---|---|---|
| Workspaces per plan | POST /workspaces | 403 { error, code: "WORKSPACE_LIMIT_REACHED", limit, total, userType } |
| Items per plan | POST /items, POST /items/upload, POST /copies/accept/:id | 403 { error: "Item limit reached", limit, userType, total, owned, shared } |
| Items per plan | POST /shares/:id/accept, POST /items/:id/duplicate | 403 { error, code: "ITEM_LIMIT_EXCEEDED", upgrade_url, currentCount, limit, incomingCount } |
| Max file size per plan | POST /items/upload, POST /media/upload, POST /profile/avatar, POST /workspaces/:id/icon | 413 { error, limit, readable } |
| Storage per plan | The uploads above, POST /icons, POST /copies/accept/:id | 413 { error, currentUsage, limit, readable, userType }. Copy accept adds required. |
| Storage per plan | POST /items/:id/duplicate | 403 { error, code: "STORAGE_QUOTA_EXCEEDED", upgrade_url, estimatedBytes, currentBytes, storageLimit } |
Item content: 2 MB of editorContent, 1 MB of text | POST and PATCH under /items, POST /media/upload with itemId | 413 { error } |
| Icon size: 200 KB | POST /icons | 413 { error, limit: 204800, readable: "200 KB" } |
| Icon file type | POST /icons, POST /workspaces/:id/icon | 415 { error } |
| 500 items per binder | POST /items/:id/binder/items | 400 { error, code: "BINDER_FULL", limit: 500 } |
| 500 items per shared fence | POST /items/:id/fence/items, POST /items with fenceId | 409 { error, code: "FENCE_FULL" } |
| 500 items and 10 levels per shared fence | POST /shares | 400 with code: "FENCE_TOO_LARGE" or "FENCE_TOO_DEEP" |
| 500 moves per request | POST /items/batch/move | 400 { error: "Maximum 500 items per batch" } |
| 500 descendants per published fence | POST /items/:id/publish | 400 { error, errorCode: "TOO_MANY_ITEMS", found, maxAllowed: 500 } |
| 10 active API keys | POST /keys | 400 { error, details } |
| 100 saved views | POST /views | 400 { error } |
| Export everything once a day | GET /export, GET /export/check | 429 { error }, without Retry-After. A second export while one is downloading gets 409. |
Messages you may see: Your file size limit is 50MB. and Your plan includes 5GB of storage. (each followed by an upgrade hint on plans that have a bigger tier), This item is too large to save. The limit is 2 MB of content per item., and This text is too large to store in an item. The limit is 1 MB. A published fence walks nested fences at most 10 levels deep. Deeper fences are not shown.
Other Errors You Will Meet
| Response | What it means |
|---|---|
401 | Missing, malformed, unknown, or revoked API key. See API Conventions: IDs, Response Format, and Status Codes for the messages. |
403 {"error":"Read-only API key"} | You used a read-only key for something other than GET, HEAD, or OPTIONS. See Opal API Keys: Create, Use, and Revoke. |
403 code: "FEATURE_NOT_IN_PLAN" | API access needs the Workshop plan. |
403 code: "ACCOUNT_LOCKED" | The account is view-only. Reads still work. See Plan, Limits, and Usage in the Opal API: GET /account. |
403 CONTAINER_SHARE_REQUIRES_UPGRADE, CONTAINER_ACCEPT_REQUIRES_UPGRADE, or CONTAINER_PUBLISH_REQUIRES_UPGRADE | Sharing, accepting, or publishing binders and fences needs Studio or Workshop. |
400 code: "CONTAINER_IN_BINDER" | Binders and fences cannot go in a binder. Nothing in the request was added. See Binders API: Add, Remove, Reorder, and Duplicate. |
403 code: "FENCE_PERMISSION_REQUIRED" or "NOT_ITEM_OWNER" | A shared fence route needs a toggle you lack (requires names it), or you tried to put someone else’s item in a fence. See Fences API: Add Items, Read Children, and Move a Fence. |
403 NOT_OWNER, 400 NOT_A_MEMBER, or 409 NO_ROOM | A reassignment was refused and nothing changed: you are not the owner, the new owner has not accepted a share, or their storage has no room. See Reassign Owner API: Hand a Shared Item, Binder, or Fence to a Member. |
409 | Workspace address taken, declining a share that was already accepted, an export already running, a shared fence conflict (FENCE_FULL, FENCE_TOO_SMALL, FENCE_IN_INBOX, FENCE_NOT_PLACED), or NO_ROOM on a reassignment. |
423 code: "ITEM_TRANSFERRING" | The item, or the binder or fence it is in, is being handed to a new owner. Reads still work. Try the change again when the reassignment is done. See Reassign Owner API: Hand a Shared Item, Binder, or Fence to a Member. |
502 code: "BULK_SHARE_FAILED" | POST /shares could not create a share record. Nothing from that request was kept. |
Debug Checklist
- Check the status code, then
codeand the extra fields in the body. - On
401, check the key: it must be complete, not revoked, and sent asAuthorization: Bearer .... - On
403, check your plan withGET /account, whether the key is read-only, whether the account is locked, and whether you own the item or have the share permission. - On
413, compare your file againstplan.limits.fileSizeBytesand your remaining storage. - On
429, wait forRetry-Afterseconds. - On
423, wait for the reassignment to finish, then try again. - On
400, check required fields and the count limits above.
Next Steps
- See all status codes: API Conventions: IDs, Response Format, and Status Codes
- Check plan limits and usage: Plan, Limits, and Usage in the Opal API: GET /account
- File size limits by plan: File Size and Storage Limits by Plan: Workshop, Studio, and Day Desk
- Manage your keys: Opal API Keys: Create, Use, and Revoke
Get Help
Stuck on an API call or have a question? Ask in the Opal Discord or send us a message through the contact form.
FAQ
What is the Opal API rate limit?
100 requests per key in each 60-second window. Shares (POST /shares) and publish operations each have a separate limit of 20 per user per 60 seconds.
Why do I get 403 on a POST with a read-only key?
Read-only keys can make only GET, HEAD, and OPTIONS requests. Any other method returns 403 with {"error":"Read-only API key"}. Use a full-access key for writes.
Why do all my requests return 429, even with a valid key?
Your IP may have made 100 failed authentications in 60 seconds. Until that window resets, every request from the IP gets 429, including requests with a valid key. Wait for Retry-After seconds.
How big can an item’s content be?
2 MB of editorContent and 1 MB of text. Larger content returns 413.
How often can I export everything?
Once a day. Exporting a single workspace is not limited.
