Skip to main content

API overview

Everything in the UI is available via the REST API. JSON requests + responses, page-number pagination, deterministic error shapes, per-route RBAC enforcement. 190+ endpoints under /api/v1.

Base URL

The same host as the UI, under /api/v1. Example: https://cap.example.com/api/v1. Everything that follows uses relative paths rooted there.

Versioning

  • Major version in the path (/api/v1). A v2 would live at /api/v2 alongside v1 during a transition window.
  • No breaking changes within a major. Additive changes only: new endpoints, new optional request fields, new response fields.
  • When a breaking change is necessary, v2 opens for a deprecation window of ≥ 12 months, with Deprecation + Sunset headers on v1 responses.

Tenancy in the URL

Every project-scoped resource is under /projects/:projectId/...:

/api/v1/projects/{projectId}/certificates
/api/v1/projects/{projectId}/acme-accounts
/api/v1/projects/{projectId}/distributions

Org-scoped resources don't carry the project prefix:

/api/v1/users
/api/v1/audit-logs
/api/v1/ca-providers

There is no cross-project read endpoint. If you need cross-project data, iterate GET /projects and fan out.

Content type

  • Request: Content-Type: application/json for all mutations. The server rejects any other content type on POST/PUT/PATCH.
  • Response: application/json, UTF-8.
  • One exception: file downloads (cert bundles, exports) return the appropriate application/x-pem-file, application/zip, or application/x-pkcs12.

Pagination

All list endpoints use offset (page-number) pagination:

GET /api/v1/projects/{projectId}/certificates?page=1&page_size=20
# →
{
"certificates": [ ... ], // the key is named after the resource
"total": 137,
"page": 1,
"page_size": 20
}

page defaults to 1; page_size defaults to 20 and is clamped to a maximum of 100 (a larger value is silently reduced, not rejected). Because this is offset paging, rows inserted or deleted between requests can shift the window — a row may be seen twice or skipped. Sort explicitly and, for exports, prefer the dedicated export endpoints over walking pages.

  • limit: default 50, max 500.
  • cursor: opaque. Don't parse or manipulate.
  • Stable sort by ID — even if records are added or deleted mid-iteration, you don't skip or double-yield.

Filtering & sorting

Per-endpoint query params. The pattern:

GET /api/v1/projects/{projectId}/certificates
?status=active,renewal_failed
&expiring_within_days=30
&issuer_type=acme
&sort=expires_at
&order=asc

Comma-separated lists for OR filters. Per-field knobs documented on each endpoint in the full reference.

Error shape

error is a flat string, not a nested object. The default shape is:

HTTP 400 Bad Request
Content-Type: application/json

{ "error": "domain not under the selected zone" }

Two endpoints add structured context alongside it. A policy violation on certificate creation:

HTTP 422 Unprocessable Entity

{
"error": "policy_violation",
"message": "key type ECDSA-P256 is not allowed by this project's policy",
"violations": [ { "rule": "allowed_key_types", ... } ]
}

And a blocked delete:

HTTP 409 Conflict

{
"error": "cannot delete: resource is referenced by other resources",
"code": "DEPENDENCY_CONFLICT",
"references": [ ... ],
"suggestion": "..."
}

There is no request_id field in error bodies, and no details array — write clients against the status code plus the error string, and treat violations / references as extras present only on those two paths.

Common status codes

CodeMeaning in this API
200OK — GET / PATCH success.
201Created — POST that created a resource.
202Accepted — async action queued (e.g. cert issuance).
204No Content — DELETE success.
400Client error — malformed JSON, bad enum.
401No auth / expired auth.
403Authenticated but role insufficient.
404Resource not found (or not visible to your tenant).
409Conflict — duplicate, state precondition failed, license limit.
422Unprocessable — policy violation, validation failure.
429Rate-limited (auth failures or per-tenant quotas).
503Service temporarily unavailable (scheduler rebooting, DB primary failover).

Idempotency

Every mutation accepts an Idempotency-Key header:

POST /api/v1/projects/{projectId}/certificates
Idempotency-Key: create-api-cert-2026-04-21
Content-Type: application/json
{ ... }

Repeating the same request within 24 h returns the original response — both on success and on error. Different bodies with the same key return 409. Skip the header if you don't need the safety; the server doesn't synthesise one.

Rate limits

  • Login / refresh / OTP-verify: 10 attempts per IP per minute → 429.
  • Per-tenant quotas: absent by default. Add at your reverse proxy / API gateway for multi-tenant deployments.
  • CA-imposed limits: cert issuance respects the CA's weekly quotas (zone page surfaces them). Not the same as CertAutoPilot's own rate limit.

Request correlation

  • Every response carries X-Request-ID. Log it; it's the key into the audit log and the OpenTelemetry trace.
  • Clients can set their own request ID by sending X-Request-ID — the server echoes it.

Client libraries

Official client: none (yet), and no OpenAPI document is served — there is no /api/v1/openapi.json. Everything is plain JSON, so any curl / ky / requests / go http.Client works; the in-app API Docs page is the authoritative endpoint reference.

See also