← Qubit Desk /API
Your token

The Qubit Desk API

Everything the page does, you can do from a script. Base URL: https://api.skillsafe.ai/v1/app-api. Every response is the same envelope.

The envelope

{"ok": true,  "data": { ... }}
{"ok": false, "error": {"code": "VALIDATION_ERROR", "message": "...", "details": { ... }}}

Check ok before reading data. A job that fails is still ok: true — the failure is in data.status.

Error codes

CodeHTTPWhat it means
UNAUTHORIZED401No token, a malformed token, or a token minted for a different app.
FORBIDDEN403The token is valid but not allowed to do this.
NOT_FOUND404No such app, job or record.
VALIDATION_ERROR400The body is not the shape the endpoint wants. error.details names the field.
INSUFFICIENT_CREDITS402The balance is below min_credits for this run. /estimate is free, so a well-behaved client never sees this.
RATE_LIMITED429Too many requests. Back off; do not tight-loop.
JOB_FAILED200The job reached a terminal failed state. The envelope is still {data} -- read data.status.
INTERNAL500A platform fault. Retry with the same Idempotency-Key: the key is what stops a retry double-billing.

1. The task field comes first

Qubit Desk is one app with one system prompt and three lanes. Every request must carry a task; it selects the lane, and it is part of the idempotency key because two lanes over one circuit are two distinct runs.

taskWhat it answersCredited to
auditWill it run on this device at all? Width, gate set, layout and routing, measurements, transpiler settings, what must change before submitting.@k-dense-ai/qiskit
decohereDoes the signal survive? The duration against T1/T2, the dominant Lindblad channel, what the error budget predicts, which mitigation applies here.@k-dense-ai/qutip
budgetWhat does it cost? The shot plan and its statistical floor, the parameter-shift budget, trainability at this width, cross-vendor portability.@k-dense-ai/pennylane

An absent or unrecognised task does not error: the model picks the closest lane and names the lane it chose in lane and in headline. Do not rely on that — send the field.

One worked body per lane

All three use the same circuit and the same device, because that is the point: one work object, three questions. The body IS the input object. Do NOT wrap it in an input key: a wrapped body still answers 200 with a plausible hold, and the model then never sees a single field.

task: "audit" — Will this Bell pair run on the heavy-hex device?

{
  "task": "audit",
  "circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];",
  "device": "name: superconducting heavy-hex, 27 qubits\nqubits: 27\nnative_gates: rz, sx, x, ecr\ncoupling: 0-1, 1-2, 1-4, 2-3, 3-5, 4-7, 5-8, 6-7, 7-10\nt1_us: 180\nt2_us: 120\ngate_error_1q: 0.00024\ngate_error_2q: 0.0072\nreadout_error: 0.011\ngate_time_1q_ns: 32\ngate_time_2q_ns: 440\nreadout_time_ns: 1400\nshots_per_second: 4200\nmax_shots: 100000\nmid_circuit_measurement: yes\ndynamical_decoupling: yes\nsession_seconds: 600",
  "objective": "sampling",
  "precision": "",
  "shots": "4096",
  "iterations": "",
  "context": "I have a 10-minute session on Thursday.",
  "prescan_facts": "<the object the browser computes -- see below>",
  "operation_sample": "<up to 400 operations -- see below>"
}

task: "decohere" — Does its signal survive 1,872 ns on a device whose T2 is 120 us?

{
  "task": "decohere",
  "circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];",
  "device": "name: superconducting heavy-hex, 27 qubits\nqubits: 27\nnative_gates: rz, sx, x, ecr\ncoupling: 0-1, 1-2, 1-4, 2-3, 3-5, 4-7, 5-8, 6-7, 7-10\nt1_us: 180\nt2_us: 120\ngate_error_1q: 0.00024\ngate_error_2q: 0.0072\nreadout_error: 0.011\ngate_time_1q_ns: 32\ngate_time_2q_ns: 440\nreadout_time_ns: 1400\nshots_per_second: 4200\nmax_shots: 100000\nmid_circuit_measurement: yes\ndynamical_decoupling: yes\nsession_seconds: 600",
  "objective": "sampling",
  "precision": "",
  "shots": "4096",
  "iterations": "",
  "context": "",
  "prescan_facts": "<the object the browser computes -- see below>",
  "operation_sample": "<up to 400 operations -- see below>"
}

task: "budget" — What does 4,096 shots of it cost in device time?

{
  "task": "budget",
  "circuit": "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];",
  "device": "name: superconducting heavy-hex, 27 qubits\nqubits: 27\nnative_gates: rz, sx, x, ecr\ncoupling: 0-1, 1-2, 1-4, 2-3, 3-5, 4-7, 5-8, 6-7, 7-10\nt1_us: 180\nt2_us: 120\ngate_error_1q: 0.00024\ngate_error_2q: 0.0072\nreadout_error: 0.011\ngate_time_1q_ns: 32\ngate_time_2q_ns: 440\nreadout_time_ns: 1400\nshots_per_second: 4200\nmax_shots: 100000\nmid_circuit_measurement: yes\ndynamical_decoupling: yes\nsession_seconds: 600",
  "objective": "sampling",
  "precision": "",
  "shots": "4096",
  "iterations": "",
  "context": "",
  "prescan_facts": "<the object the browser computes -- see below>",
  "operation_sample": "<up to 400 operations -- see below>"
}

The input object, field by field

FieldTypeWhat it is
taskstring, requiredThe lane: audit, decohere or budget.
circuitstring, requiredOpenQASM 2.0 or 3.0 source. Clipped to 42,000 characters on the middle, with the cut announced in-band as a comment.
devicestringThe target device's calibration data as key: value lines. Clipped to 8,000 characters. May be empty, in which case nine of the fourteen checks report not_run.
objectivestringOne of expectation, sampling, variational, benchmark.
precisionstringTarget standard error, as a decimal string. Empty defaults to 0.01.
shotsstringShots per circuit. Empty means "derive it from the precision".
iterationsstringOptimiser steps. Variational runs only. Empty defaults to 100.
contextstringFree text. Clipped to 6,000 characters.
prescan_factsobject, requiredEverything the browser computed. The lane is told to reconcile against it and not to contradict it.
operation_sampleobjectUp to 400 operations, with their index in the circuit, gate name, qubits and line. When sampled is true this is a golden-ratio draw plus one operation of every gate name -- never a fixed stride.
retry_notestringSet only by the client's own reformat retry, when a previous reply did not parse.

prescan_facts is the part that matters. The browser computes it before any request, the lane is instructed to reconcile against it, and every flag in it must come back answered. If you are driving the API yourself you can build it however you like, but the prompt will hold your reply to whatever you put in it — so put real numbers there or send {"readable": false, "why": "..."} and let the lane say it had no arithmetic to work from.

2. The output contract

Every lane returns the same envelope; only body differs. This is the shape the page's own normalize() parses, so it is the shape to code against.

{
  "lane": "audit | decohere | budget",
  "title": "string",
  "verdict": "runnable | runnable_with_changes | needs_rework | not_viable",
  "headline": "string",
  "summary": "string",
  "findings": [
    {
      "id": "F-001",
      "severity": "blocking | high | medium | low | info",
      "area": "width | gateset | connectivity | measurement | coherence | noise | mitigation | shots | trainability | reproducibility",
      "title": "string",
      "detail": "string",
      "evidence": "string",
      "line": 42,
      "fix": "string"
    }
  ],
  "reconciliation": [
    {
      "flag_uid": "coherence-1",
      "status": "confirmed | adjusted | set_aside | not_applicable",
      "note": "string"
    }
  ],
  "context_notes": [
    {
      "claim": "string",
      "status": "honoured | contradicted | unverifiable",
      "note": "string"
    }
  ],
  "unassessable": [
    {
      "item": "string",
      "why": "string"
    }
  ],
  "body": {
    "...": "one of the three shapes below"
  }
}

Normalisation rules worth knowing, because the client applies them rather than rejecting the reply: an unrecognised verdict falls back to runnable_with_changes; an unrecognised severity falls back to medium; a line that is null, empty or non-numeric stays null and is not printed — it never becomes line 0; and a reply with neither findings nor summary is treated as a parse failure and triggers one reformat retry that reuses an idempotency key derived from the same input.

body for task: "audit"

{
  "device_fit": {
    "qubits_needed": 12,
    "qubits_available": 27,
    "statement": "string"
  },
  "gateset_plan": [
    {
      "gate": "cz",
      "uses": 33,
      "action": "decompose into rz/sx/x/ecr",
      "entanglers_after": 33,
      "note": "string"
    }
  ],
  "routing_plan": {
    "statement": "string",
    "layout_advice": "string",
    "estimated_entanglers_after": 240,
    "worst_pair": "4-5, 4 hops apart"
  },
  "measurement_review": {
    "statement": "string",
    "unmeasured": "none",
    "dynamic_needed": false,
    "dynamic_supported": "yes | no | unknown"
  },
  "transpile_settings": [
    {
      "setting": "optimization_level",
      "value": "3",
      "why": "string"
    }
  ],
  "blocking_changes": [
    "string"
  ]
}

body for task: "decohere"

{
  "duration_review": {
    "statement": "string",
    "critical_path": "9.1 us",
    "coherence_limit": "T2 = 120 us",
    "fraction": "83%"
  },
  "dominant_channel": {
    "channel": "relaxation | dephasing | gate_error | readout | crosstalk | leakage | unassessable",
    "why": "string"
  },
  "fidelity_review": {
    "statement": "string",
    "estimate": "15.0%",
    "what_it_omits": "string"
  },
  "mitigation_plan": [
    {
      "technique": "dynamical decoupling",
      "applies": true,
      "why": "string",
      "overhead": "none in shots"
    }
  ],
  "simulation_check": {
    "statement": "string",
    "model": "string",
    "what_to_simulate": "string"
  }
}

body for task: "budget"

{
  "objective_review": {
    "statement": "string",
    "what_is_measured": "string"
  },
  "shot_plan": {
    "shots_per_circuit": 40000,
    "why": "string",
    "statistical_floor": "0.005",
    "bias_note": "string"
  },
  "gradient_plan": {
    "circuits_per_step": 97,
    "steps": 150,
    "total_executions": 582000000,
    "wall_clock": "38.5 h",
    "why": "string"
  },
  "trainability": {
    "risk": "low | medium | high | unassessable",
    "why": "string",
    "what_to_measure": "string"
  },
  "portability": [
    {
      "device_family": "trapped ion, all-to-all",
      "native_entangler": "MS",
      "what_changes": "string",
      "cost_delta": "string"
    }
  ]
}

gradient_plan is null, not an empty object, when the circuit has no free parameters.

The fourteen free checks

Each is pass, warn, fail or not_run. not_run is not a pass — it means the device profile did not carry the field the check needs, and the prompt requires the lane to say so rather than treat it as clean.

keyWhat it checks
determinismThe circuit is fully enumerable
widthThe circuit fits the device's qubit count
qubit_rangeEvery operand names a qubit that exists
gatesetEvery gate is native or decomposable
connectivityEvery entangling pair is adjacent or routable
measurementEvery qubit that is used is read out
clbitNo two measurements share a classical bit
midcircuitMid-circuit measurement and classical control are supported
coherenceThe circuit finishes inside the coherence time
idleIdle time is not the dominant term
fidelityThe estimated success probability is usable
bindingNo unbound parameter reaches the hardware
shotsThe shot budget reaches the requested precision
runtimeThe run fits the session budget

3. Numbered steps

Pick a language once; the choice applies to every block on the page and is remembered.

Step 1 — a tiny client

Two headers on every call: Content-Type: application/json and Authorization: Bearer <token>. That is the whole authentication story — there is no X-App-Slug header, and sending one changes nothing.

Step 2 — get a token

The slug travels in the POST /guest body, and that call answers 201. A guest token is enough for /me and /estimate; a metered lane needs a personal token, which you can copy from the token page without opening a developer console.

Step 3 — /me

Returns only {subject_type, subject_id, credits}. There is no user_id and no is_guest field anywhere in this API — branch on subject_type.

Step 4 — /estimate (free)

Free, creates no job, but authenticated: call it before you hold a token and you get a 401. Assert model_alias is gpt-terra and markup_bps is 1000 — that is the authoritative proof you are wired to the right model at the right markup. hold_credits is a reservation, not a price; it differs per lane, so re-estimate when you switch.

Step 5 — /run and poll (metered)

Send an Idempotency-Key on every run. Hash (task, input, attempt): the lane must be inside the key, because two lanes over one circuit are two distinct runs and must not collide. If you retry a malformed reply, reuse a key derived from the same input with a new attempt suffix — that is what stops a reformat retry double-billing.

Step 6 — /run-stream (metered, SSE)

The same body and the same key rules, delivered as Server-Sent Events. Accumulate the delta frames; the done frame carries charged_credits (the real cost, usually far below the hold) and truncated.

Recovering a truncated reply

A stream stops mid-string or mid-key far more often than it stops just after a closing brace, so appending the missing brackets to whatever arrived almost never parses. The page walks the text once, records every offset at which a complete value had just been read together with the bracket stack at that point, then tries those offsets newest-first: truncate there, drop a dangling comma, append the closers that stack needs, parse. The first one that parses is the longest recoverable prefix. Do the same rather than discarding a reply that died at 80%.

Rate limits and costs

Provenance

A derived work of @k-dense-ai/qiskit (the audit lane), @k-dense-ai/qutip (decohere) and @k-dense-ai/pennylane (budget), each credited on the lane it informs. Not a republication of those skills.