Calliope

The Writer API

Errors & conventions

The small set of rules that hold across every endpoint — how you authenticate, how tiers gate a call, and the one error shape you'll ever have to handle.

On this page

Everything in the endpoint reference follows the same handful of conventions. Learn them once.

Base URL and auth

  • Base URLhttps://app.writecalliope.ink/api/v1
  • Auth — a single bearer scheme. Send your key on every request:
Authorization: Bearer cal_your_key

Keys are created and revoked at /account/api, shown once, and stored only as a hash. See Setup.

Tiers gate the call

Every operation carries a tier that decides what it needs before it runs:

  • read — free. Always available with a valid key.
  • ai — spends your credits (or your subscription allowance). No subscription required.
  • write — requires an active subscription.

If a call isn’t permitted, you get a clear status code below — never a silent charge or a partial write.

The error shape

Errors return a stable JSON body:

{ "error": "code", "message": "a human-readable explanation" }

So you can branch on error and show message. The status codes:

StatuserrorWhen
401unauthorizedMissing or invalid API key.
402insufficient_creditsOut of AI credits — top up to continue.
403subscription_requiredThis write needs an active subscription.
403category_disabledThe relevant AI category is switched off by your spend governor.
404not_foundNot found, or you don’t own it.
400bad_requestThe request was malformed.

A 404 deliberately covers “doesn’t exist” and “isn’t yours” — the API never reveals whether a resource you don’t own exists.

Ownership

A key acts as you. Every call is scoped to the resources your account owns; you can’t read or touch another writer’s book. This is the same actor model the web app and MCP use.

The contract is the source of truth

The endpoint reference on this site is generated from Calliope’s OpenAPI contract, but the live contract itself is always authoritative:

If you’re building against the API programmatically, read openapi.json directly.