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

LimitApplies toWhen exceeded
100 requests per key per 60-second windowEvery API-key request429 with {"error":"Rate limit exceeded"} and a Retry-After header (seconds until the window resets).
100 failed authentications per IP per 60-second windowRequests with a missing, malformed, unknown, or revoked key429 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 windowPOST /shares429 with Retry-After and retryAfterSeconds in the body.
20 publish operations per user per 60-second windowPOST /items/:id/publish, POST /items/:id/publish/refresh, DELETE /items/:id/publish429 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.

LimitRoutesResponse
Workspaces per planPOST /workspaces403 { error, code: "WORKSPACE_LIMIT_REACHED", limit, total, userType }
Items per planPOST /items, POST /items/upload, POST /copies/accept/:id403 { error: "Item limit reached", limit, userType, total, owned, shared }
Items per planPOST /shares/:id/accept, POST /items/:id/duplicate403 { error, code: "ITEM_LIMIT_EXCEEDED", upgrade_url, currentCount, limit, incomingCount }
Max file size per planPOST /items/upload, POST /media/upload, POST /profile/avatar, POST /workspaces/:id/icon413 { error, limit, readable }
Storage per planThe uploads above, POST /icons, POST /copies/accept/:id413 { error, currentUsage, limit, readable, userType }. Copy accept adds required.
Storage per planPOST /items/:id/duplicate403 { error, code: "STORAGE_QUOTA_EXCEEDED", upgrade_url, estimatedBytes, currentBytes, storageLimit }
Item content: 2 MB of editorContent, 1 MB of textPOST and PATCH under /items, POST /media/upload with itemId413 { error }
Icon size: 200 KBPOST /icons413 { error, limit: 204800, readable: "200 KB" }
Icon file typePOST /icons, POST /workspaces/:id/icon415 { error }
500 items per binderPOST /items/:id/binder/items400 { error, code: "BINDER_FULL", limit: 500 }
500 items per shared fencePOST /items/:id/fence/items, POST /items with fenceId409 { error, code: "FENCE_FULL" }
500 items and 10 levels per shared fencePOST /shares400 with code: "FENCE_TOO_LARGE" or "FENCE_TOO_DEEP"
500 moves per requestPOST /items/batch/move400 { error: "Maximum 500 items per batch" }
500 descendants per published fencePOST /items/:id/publish400 { error, errorCode: "TOO_MANY_ITEMS", found, maxAllowed: 500 }
10 active API keysPOST /keys400 { error, details }
100 saved viewsPOST /views400 { error }
Export everything once a dayGET /export, GET /export/check429 { 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

ResponseWhat it means
401Missing, 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_UPGRADESharing, 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_ROOMA 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.
409Workspace 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

  1. Check the status code, then code and the extra fields in the body.
  2. On 401, check the key: it must be complete, not revoked, and sent as Authorization: Bearer ....
  3. On 403, check your plan with GET /account, whether the key is read-only, whether the account is locked, and whether you own the item or have the share permission.
  4. On 413, compare your file against plan.limits.fileSizeBytes and your remaining storage.
  5. On 429, wait for Retry-After seconds.
  6. On 423, wait for the reassignment to finish, then try again.
  7. On 400, check required fields and the count limits above.

Next Steps

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.