Amatya ID API
Amatya ID makes ID cards, event passes, tickets and membership cards, each with a QR that anyone can verify and your own gate staff can scan. This page is how you drive all of that from your own system.
Base URL: https://siddhixsys.com/api/id/v1
Get a key
Create one in the app: Amatya ID -> Developers -> API keys. The key is shown once. It carries three scopes — id:read, id:write, id:scan — and is separate from your Amatya Tests key: neither works on the other product.
curl https://siddhixsys.com/api/id/v1/me -H "Authorization: Bearer aik_..."/me answers with your plan, your caps and your current usage, so a script can pace itself instead of discovering limits by being refused.
Issue cards
A project is a batch of cards that share a design and a scan policy — one event, one class, one gym roster. Pass source_kind + source_ref and the call becomes idempotent: running your sync twice returns the same project.
curl -X POST https://siddhixsys.com/api/id/v1/projects -H "Authorization: Bearer aik_..." -H "Content-Type: application/json" -d '{"name":"Members 2026","format_key":"membership-gym",
"scan_policy":"once_per_day","source_kind":"crm","source_ref":"members"}'Then issue up to 500 cards per call:
curl -X POST https://siddhixsys.com/api/id/v1/projects/123/cards -H "Authorization: Bearer aik_..." -H "Content-Type: application/json" -H "Idempotency-Key: sync-2026-03-01" -d '{"cards":[{"external_ref":"EMP-0001",
"fields":{"full_name":"Asha Kumari","member_id":"M1"},
"valid_until":"2026-12-31T18:30:00Z"}]}'Three things worth knowing:
- `external_ref` is yours. A card posted with an
external_refthat already exists is UPDATED, not duplicated — that is what makes a nightly sync safe. You can address any card later asref:EMP-0001instead of storing our codes. - `Idempotency-Key` protects retries. If your request times out, retry with the same key: you get the original response back, not 500 more cards. Reusing a key with a different body is refused (422) rather than silently replayed.
- Every response carries `verify_url` plus
qr_url, a ready-made PNG. You do not wait on a render to print a ticket the moment a payment clears.
Partial failures come back as a per-item envelope with HTTP 207:
{ "created": 98, "updated": 1, "failed": 1,
"results": [{ "index": 0, "status": "created", "code": "8KJ2...",
"external_ref": "EMP-0001",
"verify_url": "https://siddhixsys.com/v/8KJ2...",
"qr_url": "https://siddhixsys.com/api/id/v1/cards/8KJ2.../qr.png" }] }Two semantics worth knowing before you build an updater: "update" replaces the whole fields object (it is not a merge — send every key you want kept), and null on any field means "leave unchanged", so a value cannot be cleared over the API — revoke and reissue if a card was simply wrong.
Photos
Photo ID cards need photo bytes. Upload them first, then reference the id:
curl -X POST https://siddhixsys.com/api/id/v1/assets -H "Authorization: Bearer aik_..." -H "Content-Type: application/json" -d '{"kind":"photo","data_uri":"data:image/jpeg;base64,..."}'
# -> { "asset_id": 41, "bytes": 18034, "mime": "image/jpeg" }Then "photo_asset_id": 41 on the card (and "images": {"logo": 42} for image fields such as a crest). Uploads are re-encoded server-side — photos to the exact 400x520 the card prints, logos to PNG — and identical bytes dedupe, so re-sending the same photo on every sync costs one row, not a thousand. An asset id you do not own fails that card with unknown_asset and touches nothing else in the batch.
Which fields does a design expect?
GET /formats lists every design you can point a project at, INCLUDING its field schema — key, label, type, required, and auto (a field the server assigns, like an auto-issued card number: leave it out of your payload). Map your system's columns onto those keys once and the rest is a loop.
Read, sync, revoke
| Call | What it does |
|---|---|
GET /projects/:id/cards?updated_since=&cursor= | Changed cards only, cursor-paged. Add &format=csv for a spreadsheet. |
GET /cards/:code or GET /cards/ref:EMP-0001?project=123 | One card with its live state. |
GET /cards/:code/qr.png?size=512 | The printable QR. |
PATCH /cards/:code | {"action":"revoke"}, "restore", "extend" (with days), or "update" with fields. |
GET /cards/:code/scans | Where and when that card was scanned. |
GET /verify/:code | Read-only check. Records nothing. |
POST /scans | Records a scan and returns the gate verdict. Needs id:scan. |
Renewing extends from the current expiry, so renewing a membership early never shortens it.
Rate limits arrive as headers on every response (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset), with reads and writes on separate budgets so a polling loop cannot starve a ticket sale.
Webhooks
Point a URL at us in Developers -> Webhooks and we POST when something happens: card.created, card.updated, card.revoked, card.scanned, and card.expiring_soon (your horizon, default 7 days — the membership renewal hook).
Every delivery carries X-Amatya-Signature: t=<unix>,v1=<hmac>. Verify it as HMAC-SHA256 over <t>.<raw body> using your signing secret:
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));Compare the raw body, not a re-serialised object. Failed deliveries retry with backoff and the webhook auto-disables after repeated failure — check the delivery log on the Developers screen if events stop arriving.
card.scanned fires for refusals too. "Who was turned away" is usually the number you actually wanted.
Embeds
Two widgets, one script:
<!-- status of one card, text only, links to the verification page -->
<script src="https://siddhixsys.com/embed-id.js" data-verify="CARDCODE" async></script>
<!-- live counts for one project, using a token pinned to your domains -->
<script src="https://siddhixsys.com/embed-id.js" data-stats="emb_..." async></script>The verify widget deliberately shows no tick and no badge. A frame on someone else's page can be styled to look like anything, so the authoritative answer stays on siddhixsys.com/v/<code> where the address bar can be checked. The stats tile only renders on the domains you listed when you created its token.
AI agents (MCP)
Point any MCP client at https://siddhixsys.com/api/id-mcp with your key as the Bearer token. Tools: get_limits, list_formats, create_project, issue_cards, get_card, update_card, verify_card, record_scan, expiring_cards.
"Make ID cards for everyone in this spreadsheet, valid till March, and give me the print sheet."
What the QR actually proves
A verified card's QR resolves to /v/<code>, which says the card was issued by the account that controls that URL — no more. Only an account whose identity we have checked out of band is presented as a confirmed organisation. Build your own trust decisions on that distinction, not on the presence of a QR.