← Confession Generator / API
Tokens

Driving Confession Generator from code

Base URL https://api.skillsafe.ai/v1/app-api. Every response is a {"data": …} / {"error": …} envelope. Every request carries Authorization: Bearer <token>. There is no X-App-Slug header — the token is scoped to this app.

The run body is the input object itself. Not {"input": {…}}. Wrapping it returns 200 and quietly hides every field from the model, which is the most expensive mistake available on this endpoint because nothing fails.

Errors

CodeMeaningWhat to do
UNAUTHORIZEDMissing, malformed or expired token.Mint a guest token, or sign in for a personal one.
INSUFFICIENT_CREDITSBalance below min_credits.Top up. Call /estimate first — it is free.
VALIDATION_ERRORThe body is not a valid input object.Check error.details; usually a missing briefs array.
RATE_LIMITEDToo many requests.Back off and retry. Never tight-loop.
JOB_FAILEDThe model run failed.Retry with the same Idempotency-Key; it will not double-bill.

The output contract

A single JSON object with a confessions array. Each entry carries seed (echoed back unchanged from the matching brief), label, speaker and text. aside is present only where that brief's second_beat was non-null.

{
  "confessions": [
    {
      "seed": "k3f9x2m1qp:0:8371",
      "label": "The kitchen ledger",
      "speaker": "nine years on the same floor, never once late",
      "text": "I have kept a written count of every time a colleague has taken the last of the milk without replacing it, and the figure is currently four hundred and six."
    }
  ]
}

Bind confessions to coordinates by seed, never by array index. A reply that drops one confession shifts every later index by one, and an index-bound reader then attributes every remaining confession to the wrong coordinate.

1. A tiny client

Every call is the same three things: the base URL, a bearer token, and a JSON body. This helper is used by every later step.

2. Get a token

A guest token is enough for /me and /estimate. Writing confessions is metered and needs a personal token, which comes from signing in. The easiest way to get either is the token page — it reads the token this browser already holds, with Reveal and Copy buttons, so you never have to open a developer console.

3. Check the session with /me

Returns exactly three fields: subject_type, subject_id and credits. Note what is not there — there is no email and no name. A signed-in user is one whose subject_type is the literal string "user"; anything else is a guest.

4. Price it with /estimate

Free, and it creates no job. It returns hold_credits (what is reserved, priced against the full output cap), min_credits, and the model binding. The body is the input object itself — there is no input wrapper and no X-App-Slug header. An input wrapper returns 200 and silently hides your fields from the model.

5. The input object

This is the whole contract. shape is "set" (one confession per brief) or "push" (exactly one brief, written deliberately far from everything in prior). briefs[] carries one drawn coordinate per confession, and each seed must come back unchanged in the matching confession. Every value is prose to write from, never a phrase to place in the output.

6. Run it, and poll

POST /run returns a job_id immediately. Poll GET /run/{job_id} until status is succeeded or failed. Always send an Idempotency-Key: a content hash of the input plus the shape plus an attempt counter. A retry that reuses the key cannot double-bill.

7. Stream it with /run-stream

POST /run-stream is the same run, the same body and the same Idempotency-Key, delivered as server-sent events. Frames are separated by a blank line and carry a named eventjob, delta, done, and terminally error or pending. Accumulate the text of every delta; that concatenation is the JSON object. A "seed" appearing in it means another confession has started, which is a far more honest progress signal than a character count.

A note on what the browser does that the API does not

The web app draws its own coordinates, checks the user's steer for distress and for the content boundary before spending anything, and reconciles the reply against what was drawn. Over the API you supply briefs yourself, so none of that happens for you. If you are building on this, the reconciliation logic is plain JavaScript in reconcile.js and the boundary in guard.js, both readable and both dependency-free.

Confessions are invented and spoken by people who do not exist. Do not present anything this API returns as a real disclosure by a real person.