Skip to content
Shortspan

Search documentation

Find a page by title, section, or summary.

Error codes

Every error code the API returns, what causes it, and what to do about it.

Error shape

json
{
  "error": {
    "code": "PERMISSION_DENIED",
    "message": "You do not have permission to perform this action."
  }
}

Authentication and authorization

CodeStatusCauseDo

UNAUTHENTICATED

401

Missing, malformed, expired, or revoked token.

Re-authenticate.

account_suspended

403

The account or its workspace is suspended.

Do not re-authenticate. Surface the message.

PERMISSION_DENIED

403

Role or token ability insufficient.

Do not retry. Check the role and the abilities.

CLI_VERSION_BLOCKED

403

This client version has been withdrawn.

Run shortspan upgrade, which works while blocked.

ONBOARDING_REQUIRED

409

The account has no workspace yet.

Send the user to the URL in the body.

SEPARATION_OF_DUTIES_REQUIRED

403

You cannot approve your own request or execution.

Someone else must approve it.

Device flow

CodeStatusCauseDo

authorization_pending

400

Not approved yet.

Normal. Keep polling at the given interval.

expired_token

400

The ten-minute window closed.

Start a new device code.

access_denied

400

The user refused.

Stop. Do not retry.

INVALID_CODE

400

No such device or user code.

Start over.

INVALID_TOKEN

401

The token is not recognised.

Re-authenticate.

Plan and entitlement

CodeStatusCause

PLAN_LIMIT_REACHED

402

A cap was hit. The body names limit_key, limit, current_plan, and required_plan.

FEATURE_NOT_IN_PLAN

402

The tier does not include this feature. The body names feature and required_plan.

json
{
  "error": {
    "code": "FEATURE_NOT_IN_PLAN",
    "message": "Audit logs & access history is available on the Team plan.",
    "feature": "audit_logs",
    "feature_label": "Audit logs & access history",
    "current_plan": "pro",
    "required_plan": "team",
    "required_plan_label": "Team"
  }
}

These carry enough detail to build an upgrade prompt without a second request, which is why they are richer than the rest.

Resources and validation

CodeStatusCause

NOT_FOUND

404

No such resource, or none you can see. The two are deliberately indistinguishable.

METHOD_NOT_ALLOWED

405

Wrong method for this path. Access approve and deny are PUT.

ALREADY_A_MEMBER

422

That person is already in the workspace.

INVALID_STATE

422

The resource is not in a state where this makes sense - approving an already-approved request, for example.

EMPTY_RUNBOOK

422

A runbook with no steps cannot be executed.

INVALID_EXECUTION

422

The execution does not exist, or does not belong to this runbook.

NO_CREDENTIAL

422

The target has no credential registered.

HOST_KEY_REQUIRED

422

The target requires host key verification and no pinned fingerprint is stored.

Validation failures return 422 with Laravel's standard errors map keyed by field, alongside the message.

Transport and rate limiting

CodeStatusCauseDo

HTTPS_REQUIRED

426

The request was not TLS, and not loopback.

Fix the client. Do not retry over plaintext.

-

429

Rate limited.

Back off. Retry-After says how long.

A 429 on a credential endpoint is the reveal limiter - 60 a minute, much tighter than the general 300. A fleet-wide ssh exec can reach it legitimately.

Pipeline API

CodeStatusCause

PUBLISH_UNAUTHORIZED

401

Bad or missing release token. Unset means the endpoint is closed.

INVALID_ARTIFACT

422

An artifact URL is on a host that is not allowed, or a checksum is malformed.

Three mistakes worth naming

Treating 403 as an expired credential

It is not. A client that re-authenticates on 403 loops forever against a suspended account, and looks like an attack from the server's side.

Assuming token ids are integers

They are UUID strings. Sanctum's parent model types the property as an integer, which is wrong here and has caused breakage on both sides.

POSTing to access approve and deny

They are PUT, and a POST returns 405.

Something here wrong or missing? Tell us.