Use Leona's REST API
Read published Atlas records without signing in. To start a run or read your workspace's results, create a personal access token in Account → Access tokens.
These examples use Bash, curl, jq and Python 3. They call the control plane directly; no Python package or GitHub repository access is needed. Running the submission example starts a real Leona job and uses your account's run allowance.
Set the API origin and token
The hosted origin below is the Python client's configured default. Set LEONA_API_URL to your control-plane origin when using another deployment; omit a trailing slash.
export LEONA_API_URL='https://majorana-api-nikekeixtq-uw.a.run.app'
Create the token while the intended workspace is active. Enable Also let it start verified runs for this guide. Copy the token when it appears; it is shown once. Read it into your shell without placing it in a command's history:
read -r -s -p 'Leona API token: ' LEONA_API_TOKEN
printf '\n'
export LEONA_API_TOKEN
A token expires after the selected 1–90 days and stays bound to the workspace where it was created. Switching workspaces on the website does not move it. Revoking it in Account takes effect on its next request. If you lose it, revoke it and create another.
| Scope | Useful operations |
|---|---|
read | Read workspace resources; request workflow plans and resource estimates. Always included. |
run | Start and cancel runs, execute Qapps, check circuits, and use the permitted notebook write routes. |
hardware | Submit circuits to real hardware, subject to the deployment's provider and allowance gates. Independent of run; this guide does not use it. |
A token cannot read IBM credentials or billing, manage other tokens, or delete an account. Manage credentials through the signed-in website.
Check the token's workspace before doing work:
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/me" | jq '{workspace_id, workspace_name, role}'
1. Read an Atlas record
The listing is a JSON array. view=list returns a compact projection; the individual-record request returns the full record.
curl --max-time 30 --fail-with-body -sS -D atlas.headers \
"$LEONA_API_URL/v1/catalog/entries?view=list&limit=10&offset=0" \
-o atlas.json
jq '.[] | {slug, execution_state}' atlas.json
if SLUG=$(jq -er '.[0].slug // empty' atlas.json); then
curl --max-time 30 --fail-with-body -sS \
"$LEONA_API_URL/v1/catalog/entries/$SLUG" | jq .
fi
record contains published source claims, with provenance where available. Reading a catalog entry does not execute it or establish a new verification result.
For more entries, increase offset by the number of entries received. The listing's limit is clamped to 1–500; offset is clamped to zero or greater. X-Catalog-Total in atlas.headers reports the total. If that total changes while reading several pages, restart the read; do not treat a mixed or incomplete set as the complete Atlas.
2. Start a small circuit run
To check a claim you found in Atlas, describe that claim and its assumptions in task_prompt. Start with a small reproduction.
This example asks Leona to build and check a Bell circuit. It requests an execute run explicitly, so the message is sent through the execution pipeline rather than left to automatic mode selection.
cat > run-request.json <<'JSON'
{
"task_prompt": "Build a two-qubit Bell state with H(0) and CX(0,1). Simulate 1024 shots, check the expected Bell-state behavior, and explain what was checked.",
"mode": "execute",
"framework": "qiskit",
"seed": 7,
"shots": 1024
}
JSON
RUN_KEY=$(python3 -c 'import uuid; print(uuid.uuid4())')
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $RUN_KEY" \
--data-binary @run-request.json \
"$LEONA_API_URL/v1/runs" -o run-created.json
RUN_ID=$(jq -er '.id' run-created.json)
jq '{id, workspace_id, status, queue_position}' run-created.json
A successful submission returns 201 with a run resource, usually queued. It confirms that the work was accepted, not that a circuit passed. queue_position counts claimable jobs ahead of it; it is not a time estimate. The eventual result can fail or be inconclusive.
Keep RUN_KEY and run-request.json until you receive the response. If a timeout leaves submission uncertain, retry the same POST with the same key and unchanged body. It returns the existing run rather than creating another. A changed body under the same key is refused with 409 idempotency_key_reused. A concurrent request may return 409 idempotency_key_in_flight; briefly back off, then retry the same request. Generate a new key only when intentionally starting new work.
3. Wait and inspect the evidence
Poll with a bounded wait. This example makes up to 60 checks with a two-second pause between them. Each request has a 30-second timeout. Reaching the polling limit leaves the run running; you can inspect it again or cancel it.
for attempt in {1..60}; do
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs/$RUN_ID" -o run.json || break
RUN_STATUS=$(jq -er '.status' run.json) || break
case "$RUN_STATUS" in
succeeded|failed|cancelled) break ;;
esac
sleep 2
done
jq '{status, verifier_decision, verification_summary, residual_risks, artifact_version_id}' run.json
status describes the job lifecycle. succeeded can still have a verification decision other than pass. Read verification_summary.decision, evidence_strength, checks, reason_code and unverified_claims to understand what was established. A structural pass does not establish physical correctness. A missing summary is a gap in the evidence, not a pass. Preserve the returned qualifications when presenting a result.
Read the stored event log for generated code, sandbox output and individual verification results:
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs/$RUN_ID/events?after=0" -o events.json
jq '.[] | select(.type == "code.finalized" or .type == "sandbox.result" or .type == "verification.result" or .type == "run.finished")' events.json
Each event has run_id, seq, ts and type, followed by the fields for that event type. Save the highest processed seq; subsequent reads with after=<seq> return later events. An absent event type is not evidence that its step passed.
To cancel your own queued or running run:
curl --max-time 30 --fail-with-body -sS -X POST \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs/$RUN_ID/cancel" | jq '{id, status}'
Cancellation is cooperative between executor stages. Cancelling a terminal run returns 409.
Stream events instead of polling
The authenticated SSE endpoint replays stored events and follows new ones. id is the sequence number, event is the event type, and data is the event's JSON.
curl --max-time 120 --fail-with-body -sS -N \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
-H 'Accept: text/event-stream' \
"$LEONA_API_URL/v1/runs/$RUN_ID/events/stream?after=0"
Your client should remember the last processed id. On disconnect, reconnect with that numeric value in Last-Event-ID; it takes precedence over after. For example:
LAST_SEQ=12 # replace with the last event your client processed
curl --max-time 120 --fail-with-body -sS -N \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
-H "Last-Event-ID: $LAST_SEQ" \
"$LEONA_API_URL/v1/runs/$RUN_ID/events/stream"
The example closes its connection after 120 seconds; a curl timeout does not cancel the run. Ignore SSE comment lines beginning with :. The stream closes after run.finished, at its connection-duration limit, or when it discovers a terminal run with no finishing event. Closing alone does not prove success: read the run resource and its verification summary. Use a client that can send bearer headers; the browser's built-in EventSource constructor has no custom-header argument.
Page through runs
Run lists are JSON arrays, scoped to the token's workspace. They accept limit (default 50, clamped to 1–100), an optional status, and a UUID cursor.
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs?limit=20" -o runs.json
jq '.[] | {id, status}' runs.json
if CURSOR=$(jq -er '.[-1].id // empty' runs.json); then
curl --max-time 30 --fail-with-body -sS \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs?limit=20&cursor=$CURSOR" | jq .
fi
Use the last returned ID as the next cursor, preserving the same status filter if one was supplied. Stop on an empty or short page; do not extract a cursor from an empty array. Runs are ordered by descending ID, and the cursor requests lower IDs. There is no next_cursor response wrapper.
Handle refusals and retries
Errors use application/problem+json, with type, title, status and code. Some include a machine-readable reason and additional fields. Branch on status and reason, while showing title to the person using your client.
| Response | Next action |
|---|---|
| 401 | Check the API origin and token; replace an expired or revoked token through Account. |
| 403 | Check the token's scope and the account's permission for this resource. Repeating the same request will not add authority. |
| 404 | Check the ID, deployment and token workspace. An inaccessible resource can look absent. |
| 409 | Read reason; use the same key/body for an in-flight submission, or a new key for intentionally new work. Other conflicts need their stated recovery. |
| 422 | Correct the request. Run prompts must be 1–20,000 characters; seed is 0–2³¹−1, shots is 1–20,000, and timeout_s, when supplied, is 1–600. Unknown request fields are rejected. |
| 429 | Respect Retry-After when present. Inspect the body: an exhausted account allowance needs allowance recovery, while a short rate-limit window can be retried later. |
| 503 or a transport failure | Back off with a bounded retry budget; respect Retry-After when supplied. For an uncertain run POST, retain the same idempotency key and body. |
To inspect a refusal without discarding its body, save the headers and response:
curl --max-time 30 -sS -D response.headers -o response.json -w '%{http_code}\n' \
-H "Authorization: Bearer ${LEONA_API_TOKEN:?set LEONA_API_TOKEN}" \
"$LEONA_API_URL/v1/runs/$RUN_ID"
jq . response.json
Python and coding-agent tools
The Python client, MCP server and coding-agent plugin use this API. Installing them currently requires access to the private GitHub repository. The Leona API token supplies API authority; it does not grant GitHub access. The REST examples above remain usable without installing those packages.