← Social Post Writer / API
Get a token

Drive Social Post Writer from your own code

Everything the web app does goes through one public surface. Base URL:

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

Every request carries Authorization: Bearer <token> and Content-Type: application/json. Every response is a JSON envelope: {"ok":true,"data":{…}} on success and {"ok":false,"error":{"code":"…","message":"…"}} on failure. Read error.code, not only the HTTP status.

Errors

CodeMeaningWhat to do
UNAUTHORIZEDMissing, expired or wrong-app token.Mint a guest token or sign in again.
INSUFFICIENT_CREDITSBalance below min_credits.Top up. /estimate is free and tells you the hold.
VALIDATION_ERRORThe input object failed validation.Check error.details. The body is the input object itself — never wrap it in {"input": …}.
RATE_LIMITEDToo many requests.Back off and retry.
JOB_FAILEDThe run started and did not complete.Retry with the same Idempotency-Key.

The task field: two lanes

Every input carries task. "write" turns a topic or source text into several variants, each written for every platform you name; "refine" rewrites one platform version of one variant. Every field is a string; arrays and objects travel JSON-encoded.

FieldLaneMeaning
taskboth, required"write" or "refine".
modewrite"topic" (write from an idea) or "source" (every claim comes from the pasted text).
textwrite, requiredThe topic or source text. The web app sends at most 12,000 characters, keeping the opening and the ending with a [... N characters cut ...] marker.
platformswrite, requiredJSON array, a subset of ["x","threads","linkedin","bluesky"].
formatwrite"auto", "single" or "thread".
tonebothprofessional · casual · witty · bold · educational · inspiring · storytelling · announcement · empathetic
variantswrite"1" to "4".
emojiboth"no", "some" (at most one) or "yes" (at most three).
audience, goal, ctawrite (cta both)Optional. goal is awareness · engagement · clicks · followers · hiring; cta is a link or a call to action.
voicewriteOptional: a few of your own past posts, to match rhythm and habits. Never copied.
languageboth"auto" (the language of the text) or a language name.
limitsbothJSON object of character limits, e.g. {"x":280,"threads":500,"bluesky":300,"linkedin":3000}.
platformrefine, requiredThe one platform being rewritten.
postsrefine, requiredJSON array of the current post texts (one post or a thread).
instructionrefine, requiredshorter · punchier · more_formal · more_casual · stronger_hook · add_cta · to_thread · to_single · custom
noterefineYour own instruction; required when instruction is custom.
retry_notebothSent only on the one automatic reformat retry.

The output contract

The output (output.output) is one JSON object. For write: task, status (ok · need_more · refused), message, title, variants[] (each: angle, hook_type, platforms as {"x":{"posts":[...]}, ...}, hashtags[], image_idea, alt_text), placeholders[] and notes.

Posts carry no hashtags and no "1/5" numbering: add the tags you want and number the thread yourself. Square-bracket placeholders such as [link] mark a fact that was not given; fill them in before posting. The web app counts each post the way its platform does (on X a link counts as 23 and CJK or emoji as two) and offers a split when one runs over.

For refine: task, status, message, platform, posts[], hashtags[], change_note.

1. Get a token

A guest token is enough for /me and /estimate. Running is metered and needs a personal token from signing in. The tokens page shows yours and copies it. Replace YOUR_TOKEN below with it.

2. Check the session and the balance

/me returns subject_type, subject_id and credits. Signed in means subject_type === "user".

3. Price it before you run it

/estimate is free and starts no job. hold_credits is a reservation priced against the longest possible answer, not the price. Estimate each lane you use. Write:

Refine (worked example for the second lane):

4. Run and poll

/run returns a job. Always send an Idempotency-Key — the same key is the same run, so a retry cannot bill twice. The web app derives it from the lane and a hash of the input.

5. Stream it

/run-stream answers with server-sent events and ends with a done frame carrying the whole output, charged_credits and truncated. If truncated is true the balance ran low mid-answer: parse what arrived and top up for the rest.