docs — the whole api

one endpoint.
ten minutes.

No SDK, nothing runs on the device side. You hold an API key, you name a box, you pass a URL. This page is the entire integration — the machine-readable version is openapi.json in the console repo.

1 · get your key

Create an API key in the console with the devices:trigger scope. It's shown once — store it like a password. Every request carries it as a bearer token.

2 · trigger a box

One POST. The Idempotency-Key header is required — retry with the same key and you get the stored response back, never a double charge.

$ curl -X POST https://<your-console-host>/api/v1/devices/dev_8f21/trigger \ -H "Authorization: Bearer sk_live_…" \ -H "Idempotency-Key: 9f3a-…" \ -H "Content-Type: application/json" \ -d '{ "action": "show_qr", "payload": { "url": "https://yourshop.example/r/8f21" } }'
202 · { "id": "cmd_…", "status": "queued" } — one credit is now on hold

The URL must be http(s), at most 2048 characters. We never fetch, render, or store what's behind it — it goes straight into the QR.

what can be behind the url — anything

The URL is the whole contract. We never fetch, inspect, or constrain what's behind it — a receipt, a warranty registration, a loyalty card, a ticket, a survey, a menu all travel through the same show_qr trigger, cost the same one credit, and fail the same honest way. If a phone browser can open it, the box can hand it over. New interaction types are pages you build, not endpoints you wait for.

3 · how you know it worked

202 means queued, not shown. Within a couple of seconds the box displays the code and confirms it — that confirmation spends the held credit. If the box can't confirm within 60 seconds, the request expires and the hold is released in full. A late code never appears.

There is no status endpoint or callback yet — that's deliberate minimalism at this stage, not an accident. Billing is self-reconciling (you're only ever charged for confirmed showings); the console's activity and credit ledger show every command's outcome, and GET /api/v1/usage returns your organisation's usage programmatically. If your integration needs a per-command status call, tell us — it's on the roadmap.

every answer the api gives

202queued; one credit on hold, settled on the box's confirmation
400missing_idempotency_keyadd the Idempotency-Key header
401invalid_api_keybad or revoked bearer token
402insufficient_creditsbalance is empty — top up in the console; nothing was charged
403insufficient_scopekey lacks devices:trigger
404device_not_foundunknown device, or not in your organisation
409device_offlinebox offline or paused — rejected before any credit is held
409conflictsame Idempotency-Key is being processed right now
422invalid_requestmalformed JSON, or the url isn't valid http(s) ≤ 2048 chars
429fair_use_exceededper-device monthly ceiling (non-credit plans) — honours Retry-After
503transport_unavailablebroker hiccup — safe to retry with the same Idempotency-Key

bonus: pin a durable code

Beyond the transient trigger, a box (or a whole store, or the whole organisation) can hold a pinned QR — a menu, a wi-fi login, a review link that stays up until you change it. POST /api/v1/devices/{id}/pin (also /stores/{id}/pin, /org/pin). Setting a pin spends one credit immediately — a pin can't fail to display, so there's nothing to hold.

stuck, or missing an endpoint?

The console and API are developed in the open. Read the source, open an issue, or just write — the person answering is the person who wrote the code.

github.com/erenmeren/ditto-admin · erenaltan@gmail.com

ask a question