Skip to content
Shortspan

Search documentation

Find a page by title, section, or summary.

API overview

Base URL, versioning, response envelopes, rate limits, and the transport rules the API enforces.

Base URL and versioning

text
https://shortspan.dev/api/v1/

The version is in the path. A breaking change gets a new version rather than a silent alteration of this one; within v1, endpoints and fields may be added but existing ones will not change shape.

This is the same surface the CLI uses. Anything the CLI can do, your own tooling can do - and the CLI is a reasonable reference implementation when a detail here is ambiguous.

Transport

TLS is required. A plaintext request is refused with 426 Upgrade Required and code HTTPS_REQUIRED - it is not redirected, because a redirect means the credential was already sent in the clear.

Loopback is exempt so a local development deployment works.

Client version header

text
X-Shortspan-CLI-Version: 1.4.2

The CLI sends this on every request. It is what lets a release with a security defect be withdrawn centrally: a blocked version is refused with 403 CLI_VERSION_BLOCKED. Your own client need not send it, and is never blocked by it.

Response shape

Success responses carry a data key. A single resource is an object; a collection is an array.

json
{
  "data": {
    "id": "9f2c1e04-7b3a-4d51-8e6f-2a9c4b1d7e30",
    "key": "DATABASE_URL",
    "type": "env",
    "format": "string",
    "template": "env",
    "metadata": null,
    "created_at": "2026-08-14T09:31:02.000000Z",
    "updated_at": "2026-08-27T16:02:44.000000Z"
  }
}

Errors carry an error object with a stable code and a human message. Some errors add fields - a paywall response names the limit and the plan that lifts it.

json
{
  "error": {
    "code": "PLAN_LIMIT_REACHED",
    "message": "Your Free plan allows 3 projects. Upgrade to add more.",
    "limit_key": "projects",
    "limit": 3,
    "current_plan": "free",
    "required_plan": "pro"
  }
}

Ids are UUID strings throughout - including token ids, which Sanctum's parent model incorrectly types as integers. Treat every id as a string.

Status codes

StatusMeans

200

Success.

201

Created.

204

Success, no body - deletions.

400

Malformed request. Also the device flow's authorization_pending.

401

Missing, invalid, or revoked token.

402

The workspace's plan does not include this. The body names the required tier.

403

Authenticated but not permitted - role, ability, suspension, or a blocked CLI version.

404

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

405

Wrong method for this path.

409

Onboarding is not finished, so there is no workspace to act in.

422

Validation failed. The body carries an errors map keyed by field.

426

Not TLS.

429

Rate limited. Retry-After says for how long.

500

Our fault. Report it.

Rate limits

LimiterBudgetKeyed by

General API

300/minute

Token, or IP when unauthenticated.

Credential reveal

60/minute

Token. Applies to every endpoint that returns a decrypted value.

Device code

20/minute

IP.

Device token poll

30/minute per code, 240/minute per IP

The device code, and the IP.

The reveal limiter covers secret reveal and injection, SSH and database credential issue, AWS injection, and kubeconfig fetch - every path that produces plaintext. Reading secrets in bulk is what a stolen token looks like, so it is bounded separately and much more tightly than everything else.

Pagination and filtering

Collections scoped to a project - secrets, targets, runbooks - return in full; a project holding enough of them to need pages is a signal to split the project. The audit log is paginated, since it grows without bound.

Filters are query parameters where they exist, for example ?group=api-production when listing secrets.

Something here wrong or missing? Tell us.