← Graph Desk / API
Get a token

Driving Graph Desk from your own code

Everything the web app does is available over HTTP. Paste an edge list, pick a task, and get back one JSON object. The deterministic graph algorithms the browser runs for free - degree, components, cut-vertices, bridges, centrality - are not run server-side, so if you drive the API directly you should send your own prescan facts: that is what the model is held accountable to.

Base URL and headers

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

One header on every request:

The token is app-scoped, so the slug is not a header. There is no X-App-Slug header — a token minted for this app addresses this app and nothing else. The slug appears in exactly one place: the body of POST /guest, which is how you get a token in the first place.

curl -sS -X POST https://api.skillsafe.ai/v1/app-api/guest \
  -H "Content-Type: application/json" \
  -d '{"slug": "graph-desk"}'

# {"ok":true,"data":{"token":"aut_...","guest_id":"gst_...","expires_at":"..."}}

A guest token is enough for /me and /estimate. Running either lane is metered and needs a personal token, which comes from signing in on the token page.

The body of /estimate, /run and /run-stream is the input object itself, not wrapped in an input key. Its fields are listed under step 4 below.

The response envelope

Every response has the same two shapes. Branch on error.code, never on the message text — messages are for humans and will change.

// success
{"ok": true, "data": { ... }}

// failure
{"ok": false, "error": {"code": "VALIDATION_ERROR",
                     "message": "human-readable",
                     "details": { ... }}}

Error codes

codeHTTPWhat it means and what to do
UNAUTHORIZED401No token, a malformed token, or a token for a different app. Mint a new one from the token page.
FORBIDDEN403A guest token on a metered lane. Sign in for a personal token, or ask the publisher to enable sponsorship.
NOT_FOUND404The job id does not exist, or the token belongs to a different app.
VALIDATION_ERROR400The input failed validation. error.details names the offending field - usually task set to something outside structure/brief.
PAYMENT_REQUIRED402The balance is below min_credits. Never let a user reach this: compare hold_credits against /me first.
RATE_LIMITED429Too many requests. Back off and retry with a growing delay; the app-api budget is shared across your whole account.
INTERNAL500A platform fault. Retry once with the same Idempotency-Key so you are not billed twice.

1. A tiny client helper

Two headers on every call: the bearer token and, where the call takes a body, the content type. Success is always {"ok": true, "data": {...}}; a failure carries error.code, so branch on the code and not on the message text.

2. Who am I, and can I afford it

GET /me is free. subject_type is user for a personal token and guest for an anonymous one. Only a personal token can run either lane, and credits is the balance you compare the hold against.

3. The input fields

The same object goes to /estimate, /run and /run-stream. task selects the lane and comes first.

fieldtyperequiredwhat it is
taskstringyes"structure" or "brief".
edge_liststringyesOne relationship per line: A -> B, A <- B, A <-> B, A,B, tab/semicolon/pipe-separated, or A - B (spaces required around the bare dash). An optional trailing weight: A -> B : 3.5. A bare line with just a name declares an isolated node.
ambiguous_is_directedbooleanyesHow to read a comma/tab/semicolon/pipe/dash pair that carries no arrow. Explicit arrows always win regardless of this flag.
purposestringnoOne of org-chart, dependency-graph, supply-chain, social-network, citation-network, other. Only read by the brief task; it governs language, never arithmetic.
prescanobjectyesThe exact structural facts the app's free browser engine computed - counts, degree, components, directed facts, cycle, articulation points, bridges, clustering, closeness, betweenness, distance, and a flags array. Driving the API directly means computing and sending this yourself; it is what the model is held to. See graphlib.js in the bundle for the exact shape, or run the app once and read the network tab.
prior_structureobjectnobrief task only. The structure task's own prior output on the same graph - verdict, key_nodes, and a trimmed findings list - so the two lanes read as one sitting.
clip_notestringnoSet when the app clipped the pasted edge list to fit the run; tells the model how much was cut.

4. Price it before you run it

POST /estimate is free and creates no job. It returns the model binding and hold_credits - the amount reserved, which is almost always more than the settled charge because the hold prices the full output cap. The hold differs per lane, so re-estimate whenever you change task.