← Pulse Desk / API
Get a token

Driving Pulse Desk from your own code

Everything the web app does is available over HTTP. Paste a time series, pick a lane, and get back one JSON object. The deterministic statistics the browser runs for free - the OLS trend and its significance test, autocorrelation seasonality, the exact single-changepoint search, z-score anomalies, the volatility-regime read, the naive forecast - are not recomputed 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": "pulse-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 3 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 diagnostics/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, and where the token comes from

Get the token first: open the token page, sign in, and copy it — that page reads and writes the token this browser already holds for pulse-desk, so no developer console is involved. Then 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"diagnostics" or "brief". The full lane list is exactly those two: diagnostics reads the shape the free engine measured and says what it means; brief turns that reading into a decision brief for a stated purpose. Anything else is a VALIDATION_ERROR.
series_textstringyesThe pasted series, one point per line, as timestamp,value (comma, tab or whitespace separated) or a bare value with no timestamp at all. A header row such as date,value is auto-detected and skipped. A missing value is NA, null, or simply blank.
value_labelstringnoFree text naming what the metric is, e.g. "daily p95 API latency, in milliseconds". It governs wording and unit-awareness, never arithmetic.
purposestringnoOne of ops-monitoring, business-kpi, quality-control, capacity-planning, other. Read by the brief lane, which echoes it back in body.purpose.
prescanobjectyesThe exact deterministic statistics the app's free browser engine computed. Driving the API directly means computing and sending this yourself; it is what the model is held to. See the field list just below.
prior_diagnosticsobjectnobrief lane only. The diagnostics lane's own prior output on the same series - verdict, key_signals, and a trimmed findings list - so the two lanes read as one sitting. This is the handoff the web app's button performs.

The prescan object

One object, ten keys, all computed before any model call. The two lanes above are the teaching surface of this page; prescan is plumbing you copy from the engine rather than hand-write, so here it is at the level of its keys. Run the free engine once on skillsafe.ai/pulse-desk and read the network tab, or read pulselib.js in the bundle, for every leaf field. The worked example in step 7 shows one real, complete prescan.

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. Both lanes are shown below.