← Problem Solver / API
Get a token

Drive Problem Solver 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 the HTTP status alone.

Errors

CodeMeaningWhat to do
UNAUTHORIZEDMissing, expired or wrong-app token.Mint a guest token or sign in again. A cold 401 from /me before any token exists is normal.
INSUFFICIENT_CREDITSBalance below min_credits.Top up. Call /estimate first — it is free and tells you the hold.
VALIDATION_ERRORThe input object failed validation.Check error.details. The run body is the input object itself — do not wrap it in {"input": …}.
RATE_LIMITEDToo many requests.Back off and retry. Do not tight-loop.
JOB_FAILEDThe run started and did not complete.Retry with the same Idempotency-Key so a partial charge is not doubled.

The input object

Problem Solver is a single-contract app. There is no task field and no lane router; the same contract handles a first pass and a follow-up, distinguished by shape.

FieldTypeRequiredWhat it is
shapestringyes"first-pass" or "revision".
problemstringyesThe problem in the person's own words. The web app clips anything over 14,000 characters from the middle, keeping both ends, and marks the cut in-band.
problem_clippedbooleannoWhether the text above was clipped.
factsobjectnoThe free client-side pass: stated_constraints, stated_goals, already_rejected, other_parties, time_expressions, quantities, local_classification, specificity_band, missing_pieces. Send it and the breakdown is held to covering it; omit it and you lose the reconciliation.
professional_domainsstring[]noAny of medical, legal, financial, mental_health. Binds the boundary rules hard.
frame_overridestringnoForce a problem type instead of letting the breakdown classify.
priorobjecton revisionThe previous breakdown's restated, problem_type, first_move, options, constraints, signals_working, signals_not_working.
updatestringon revisionWhat actually happened since.
update_kindstringon revisiontried_it, constraint_changed or new_information.

The output contract

One JSON object, no prose and no code fence. Top-level keys, all required: title, problem_type, type_confidence, type_rationale, restated, reframe, optimising_for, constraints, unknowns, options, frame, first_move, signals, out_of_scope, delta, input_notes.

The web app's parser tolerates a truncated reply: it walks the fragment, discards the uncompletable tail and closes what is open, then renders the sections that arrived. If you write your own client, do the same rather than discarding a 90%-complete stream.

1. Get a token

Every call needs one. A guest token is minted automatically and is enough for /me and /estimate; running a breakdown is metered and needs a personal token from signing in. The tokens page shows yours, copies it, and mints a fresh guest token — no developer console needed. In the samples below, replace YOUR_TOKEN (or set PD_TOKEN in your shell) with it.

2. Check the session and the balance

/me returns exactly three fields: subject_type, subject_id and credits. There is no email and no name — the signed-in test is subject_type === "user".

3. Price it before you run it

/estimate is free, starts no job and charges nothing. It returns hold_credits (a reservation priced against the full output cap, not the price), min_credits, model and model_alias. Estimate every input shape you submit — a first pass and a revision are structurally different bodies.

4. Run it and poll

/run submits and returns a job. Always send an Idempotency-Key: a retried request with the same key is the same run, so a network blip cannot bill you twice. Poll /jobs/{id} until status is succeeded or failed; the breakdown is the JSON string at output.output.

5. Or stream it

/run-stream is the same run as server-sent events. delta frames carry text as it arrives; the final done frame carries output.output, charged_credits and truncated. If truncated is true the balance sat between min_credits and hold_credits and the output cap was reduced — render what arrived and say so.