← Poetry Generator / API
Your token

Driving Poetry Generator from your own code

Poetry Generator is a SkillSafe app, so everything the web page does is available over HTTP. There is one contract in each direction: you post a subject, a mood and a form, and you get back one JSON object containing the poem and the model's own account of it. This page documents it exactly as app.js implements it.

Base URL: https://api.skillsafe.ai/v1/app-api

What the API cannot give you, and it is the important part. The scanner does not run here. Counting the lines, deriving the rhyme scheme from the endings, checking stress against the metre and finding the refrains all happen in the browser, in prosody.js and forms.js. Over the API you receive the model's claimsclaimed_scheme, claimed_metre, turn_at_line — and you should treat them the way this app does: as assertions to be checked, not as measurements. The two files are plain JavaScript with no dependencies and no network calls; if you want the check, take them.

The envelope

Every response is {"data": ...} on success or {"error": ...} on failure.

{"data": {"job_id": "job_...", "status": "succeeded", "charged_credits": 741, "output": {"output": "{...}"}}} {"error": {"code": "INSUFFICIENT_CREDITS", "message": "balance below the minimum for this run"}}

1. A token

Every call needs a bearer token. A guest token is free, needs no account, and is enough for /estimate. Writing a poem is metered and needs a real account token — see the token page.

2. Who the token belongs to

/me returns exactly three fields: subject_type, subject_id and credits. There is no email, no name and no id beyond the subject id, so the test for "signed in" is subject_type === "user".

3. Pricing a run

/estimate is free and returns the credits that will be held. You are charged for what the run actually uses, which is usually well under the hold.

A warning worth acting on. This endpoint performs no validation of the request body. A bare string, a number, null and [] all return ok: true with a well-formed estimate and a correct model binding — a malformed body and a correct one return the same hold. So a successful estimate tells you nothing whatever about whether your input was shaped right. Validate on your side before you send; this app ships a mustBeObject() guard on every path that spends, for exactly this reason.

4. Writing a poem

The request body is the input object itself — there is no wrapper field.

{ "subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": "" }

form is one of sonnet, villanelle, ballad, blank, free, ghazal, limerick. strictness is strict or loose; under loose the model may bend the form and must then list where in departures.

What comes back

output.output is a JSON string. Parse it and you get:

{ "title": "", "reading": "", "form": "sonnet", "poem": { "lines": ["", ""], "stanza_breaks_after": [], "claimed_scheme": "ABABCDCDEFEFGG", "claimed_metre": "iambic pentameter", "turn_at_line": 9, "refrain_lines": [], "radif": "" }, "on_the_form": "", "departures": [], "craft_notes": [], "set_aside": [{"line": "", "why": ""}] }

poem.lines is one array element per line, with no blank strings; stanza breaks are in stanza_breaks_after as 1-based line numbers. claimed_scheme is one letter per line. Every one of these is the model describing its own work, which is why the page checks them rather than printing them.

5. Streaming

/run-stream sends the same JSON as a series of deltas. The page uses it to show progress; the parsed result is identical.

6. Past poems

Poems saved from the page live in a declared collection called poems, readable only by their owner. Records nest their document under doc — read record.doc.title, never record.title.

7. Finding one by meaning

/similar searches the embedded fields (title, subject, opening) semantically.

Note the shape difference. /query resolves to {records, next_cursor}; /similar resolves to the records array itself. Reading .records off a similarity result yields undefined and turns every search into a silent "no matches", which is indistinguishable from genuinely having none.

Errors

INSUFFICIENT_CREDITS — balance below the run minimum. UNAUTHORIZED — missing or expired token; mint a new guest token. RATE_LIMITED — back off and retry; /similar is limited more tightly than /query.

Rate and size

The subject is clipped at 4,000 characters and the notes at 1,200 before the request is built, and the page tells the user how much was cut. A collection document is capped at 64 KB.