heydecks

API

Errors

Every non-2xx response uses the same error envelope.

#Error envelope

Every non-2xx response returns a JSON object with a single error key:

JSON
{
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits: need 10, have 3 remaining."
  }
}

code is a stable machine-readable identifier. message is a human-readable description that may include contextual detail.

#Error codes

CodeHTTPWhen
invalid_input400The request body isn't valid JSON, or isn't a JSON object. A malformed request, as opposed to a well-formed one that fails validation (422).
invalid_key401Missing, malformed, or revoked API key.
subscription_past_due402The account's last payment failed. Update the card to resume API access.
insufficient_credits402Not enough credits remaining this billing period.
deck_limit402Deck count is at the plan limit. Upgrade to create more.
brand_limit402Brand count is at the plan limit. Upgrade to create more.
pptx_not_on_plan403POST /v1/decks/{id}/exports with format: "pptx" on a plan without PPTX export. Export PDF, or upgrade to Solo or higher.
not_found404The resource doesn't exist or belongs to a different user.
idempotency_conflict409An Idempotency-Key on POST /v1/generate was reused with a different body.
invalid_input422Well-formed JSON that failed validation: a missing or too-long field, an unknown enum value, more than 12 or an unrecognised keys value on GET /v1/slide-templates.
rate_limit429Over the request rate for your plan. Retry-After and the RateLimit-* headers say for how long.
internal_error500An unexpected server error. Safe to retry with backoff.
ai_unavailable502The AI step on POST /v1/decks/{id}/slides (prompt mode) or POST /v1/decks/{id}/edit failed. Credits are refunded. Retry with backoff.
generation_failed502POST /v1/images returned no usable image. Credits for the failed image are refunded.

The API is available on every plan, including free. Access is gated on the account's billing being in good standing (subscription_past_due), on credits (insufficient_credits), on plan resource limits (deck_limit, brand_limit), and per feature (pptx_not_on_plan) — never on simply having a paid plan.

generation_failed is also the error_code on a failed GET /v1/jobs/{id}. There it covers any failure of the background generation: the job runs after the HTTP response was already sent, so the failure surfaces on the job rather than on the original request. Credits are refunded.

#Rate limits

Every v1 request is counted, not just the expensive ones. There are two windows, and a request may be charged to both:

BucketWindowApplies toCapacity
standard10sEvery v1 request, reads included.30 × your plan's per-second rate.
ai5sPOST /v1/generate, POST /v1/images, POST /v1/brands/extract, POST /v1/decks/{id}/edit, and POST /v1/decks/{id}/slides in prompt mode.5 × your plan's per-second rate.

Free plans are 1 request per second, so in practice: 30 requests per 10 seconds overall, and 5 AI requests per 5 seconds. Paid plans scale from there.

A 429 carries Retry-After (seconds), RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. Honour Retry-After instead of retrying immediately. Minting an export or preview link is a standard-bucket request: the render it triggers is charged to nobody, but the mint still counts.

#Handling 402 errors

A 402 covers four distinct situations — always check error.code:

  • subscription_past_due — a payment failed. Update the card at heydecks.com/dashboard/billing. Retrying without fixing billing will keep failing.
  • insufficient_credits — you've used your monthly allowance. Wait for the next billing period or upgrade.
  • deck_limit — you've reached the maximum deck count for your plan. Delete a deck or upgrade.
  • brand_limit — you've reached the maximum brand count for your plan. Delete a brand or upgrade.

#Retrying

Safe to retry (idempotent reads):

  • GET /v1/decks, GET /v1/decks/{id}, GET /v1/jobs/{id}, GET /v1/brands, GET /v1/slide-templates

Not safe to retry without checking first:

  • POST /v1/generate — each call deducts 100 credits per deck and creates a new job.
  • POST /v1/decks/{id}/publish — idempotent by effect but each call creates a version snapshot.
  • POST /v1/decks/{id}/exports and POST /v1/decks/{id}/previews — free and harmless to repeat, but each call mints a new link. The previous one stays valid until it expires, so retrying in a loop leaves live download links behind.

500 internal_error responses are safe to retry with exponential backoff.