Help center

Errors

Errors share one shape, an HTTP status plus a stable error code. Branch on the code, show the message to people, and retry only 429 and 500.

Business plan Updated
On this page
  1. Error shape
  2. All statuses and codes
  3. Example bodies
  4. 401 missing_key
  5. 401 creator_not_admin
  6. 401 key_in_url
  7. 403 plan_required
  8. 404 not_found
  9. 405 method_not_allowed
  10. 422 invalid_parameter
  11. 422 already_assigned
  12. 429 rate_limited
  13. 500 internal_error
  14. The order checks run in
  15. Handling errors well

Error shape

Every error, from every endpoint, has the same body:

JSON
{
  "error": {
    "code": "read_only_key",
    "message": "This API key is read only. Create a read and write key to make changes."
  }
}
Field Type Meaning
error.code string A stable, machine-readable code. Branch on this.
error.message string A sentence for people. It may change at any time, so don’t parse it.

The HTTP status says what kind of error it is. New codes may be added within v1, so treat an unknown code by its status.

Creating a key happens in the app, not through the API, so it has no error code here. The app refuses to create a key when the workspace isn’t on the Business plan, has 25 active keys, or its subscription has ended.

All statuses and codes

Status Code Meaning Retry?
400 invalid_json The body isn’t valid JSON, or is JSON but not an object. No
401 missing_key No Authorization header, or it isn’t Bearer <key>. No
401 invalid_key The key is malformed (not sc_live_ plus 64 hex characters) or doesn’t exist. No
401 revoked_key The key was revoked. No
401 creator_not_admin The person who created the key was removed from the workspace or is no longer an owner or admin. No
401 key_in_url A key appeared in the query string. Revoke it. No
403 plan_required The workspace isn’t on the Business plan. No
403 read_only_key A read only key called a POST endpoint. No
403 workspace_read_only The workspace’s subscription has ended, so writes are refused. Reads still work. No
403 library_read_only POST /sops into a library shared in from another workspace. No
404 not_found No such SOP or library in this workspace. Something in another workspace is always 404, never 403. No
404 unknown_endpoint No such endpoint under /api/v1. Check the path. No
405 method_not_allowed The path exists but not with this method. The Allow header lists the methods it takes. No
415 unsupported_media_type A POST without Content-Type: application/json. No
422 invalid_parameter A parameter or body field has the wrong type or format, or is out of range. The message names it. No
422 invalid_cursor The cursor isn’t one the API issued. No
422 invalid_query q on GET /search is empty or longer than 200 characters. No
422 invalid_status status on GET /assignments isn’t open, overdue or completed. No
422 invalid_title title on POST /sops is missing, blank or longer than 200 characters. No
422 invalid_description description on POST /sops is longer than 2,000 characters. No
422 invalid_body POST /sops needs exactly one of markdown or text, as a non-empty string. No
422 invalid_note note on POST /assignments isn’t a string, or is longer than 1,000 characters. No
422 too_large The new SOP is over 500,000 characters, over 50 sections, or over about 1 MB once converted. No
422 not_a_member Nobody in the workspace signs in with that email. Invite them in the app first. No
422 already_assigned The person already has this SOP assigned and not yet completed. No, it already exists
422 sop_archived The SOP is archived. Restore it in the app before assigning it. No
429 rate_limited Over 60 requests this minute for this key. Yes, after Retry-After
500 internal_error Something failed on our side. Yes, with backoff

Example bodies

401 missing_key

JSON
{
  "error": {
    "code": "missing_key",
    "message": "Send your API key as: Authorization: Bearer sc_live_..."
  }
}

401 creator_not_admin

JSON
{
  "error": {
    "code": "creator_not_admin",
    "message": "The person who created this API key is no longer an admin of this workspace, so the key stopped working. An admin can create a new one."
  }
}

Promote the creator back to admin, or have a current admin create a replacement key. See Keys follow their creator.

401 key_in_url

JSON
{
  "error": {
    "code": "key_in_url",
    "message": "API keys go in the Authorization header, never in the URL. Revoke this key in the app, since URLs are logged."
  }
}

403 plan_required

JSON
{
  "error": {
    "code": "plan_required",
    "message": "The API is part of the Business plan. A workspace owner can switch plans in People, Settings & billing."
  }
}

404 not_found

JSON
{
  "error": {
    "code": "not_found",
    "message": "No SOP with that id in this workspace."
  }
}

405 method_not_allowed

Shell
HTTP/2 405
allow: GET, POST
JSON
{
  "error": {
    "code": "method_not_allowed",
    "message": "Use GET or POST here."
  }
}

422 invalid_parameter

JSON
{
  "error": {
    "code": "invalid_parameter",
    "message": "limit must be a whole number from 1 to 100."
  }
}

422 already_assigned

JSON
{
  "error": {
    "code": "already_assigned",
    "message": "That person already has this SOP assigned and not yet completed."
  }
}

429 rate_limited

JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. The limit is 60 a minute per key."
  }
}

500 internal_error

JSON
{
  "error": {
    "code": "internal_error",
    "message": "Something went wrong on our side. Try again."
  }
}

The order checks run in

A request is checked in this order, and the first failure is the one you get:

  1. The path and method (404 unknown_endpoint, 405 method_not_allowed).
  2. A key in the URL, then the Authorization header’s form (401 key_in_url, 401 missing_key, 401 invalid_key).
  3. For POST, the content type and JSON body (415, 400).
  4. Parameters and body fields (422).
  5. The key itself: exists, not revoked, creator still an admin (401).
  6. The rate limit (429).
  7. Plan, then for writes the key’s scope and the workspace’s billing (403).
  8. The request itself: what it names exists and can be used (404, 403 library_read_only, 422).

Because step 4 runs before step 5, a request with a bad parameter answers 422 even when the key is also invalid. Only steps 6 onwards count against the rate limit.

Handling errors well

  • Branch on error.code, not on message and not only on the status.
  • Retry only 429 and 500. Every other error is the same on retry. See backoff code.
  • POST retries are safe to reason about. A 429 or a 4xx changed nothing. POST /assignments refuses a duplicate with 422 already_assigned, so a retried assignment never assigns twice; treat that code as “already done”. POST /sops has no such guard: if a request timed out without a response, check with GET /sops?updated_since= before sending it again.
  • Alert on 401 and 403. They mean a person must act: a revoked key, a creator who lost admin, or a plan change. Retrying won’t fix them.
  • Log X-SOPCompass-API-Version alongside errors so you can tell which API version answered.