API reference
Developer docs
Mint codes programmatically, embed the form, hook Stripe + WooCommerce events.
Introduction
The API lets a reseller's backend (WooCommerce, custom store, CRM) mint access codes from the reseller's own quota. There are two endpoints:
GET https://getcid.livezentech.com/api/reseller/quota— read remaining balancePOST https://getcid.livezentech.com/api/reseller/mint— atomically mint N codes
All requests return JSON. All endpoints expect Bearer token auth in the Authorization header (or X-Api-Token).
Base URL: https://getcid.livezentech.com
Authentication
Resellers create their own tokens at /account/api-tokens. Each token is scoped to your account and carries:
- A name (for your reference)
- An optional IP allow-list (recommended for stable store IPs)
- A rate limit (req/hour, default 1000, max 10000)
The plaintext value is shown once at creation — copy it to a secure secret store immediately. To rotate, create a new token and revoke the old one.
Authorization: Bearer 1a2b3c4d5e6f...
Treat the plaintext token like a password
GET/api/reseller/quota
Returns the reseller's remaining code allowance. Cheap, idempotent, can be polled freely.
curl https://getcid.livezentech.com/api/reseller/quota \
-H "Authorization: Bearer YOUR_RESELLER_TOKEN"
200 OK
{
"ok": true,
"granted": 50,
"used": 3,
"remaining": 47,
"reseller": { "email": "[email protected]", "name": "Acme Inc." }
}
POST/api/reseller/mint
Atomically draws quantity codes from the reseller's quota and returns them. If the quota was changed by a concurrent request between read and write, the call returns 409 race_lost — retry once. Codes are not emailed unless send_email = true AND customer_email is set.
Always pass a reference
reference to your internal order ID (e.g. wc_order_4821). We store it on the audit log so refunds and disputes can be traced back to the originating order with one query. Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| quantity | int | yes | 1 to 100 per call |
| customer_email | string | no | If set + send_email, codes are emailed to this address |
| reference | string | no | Your internal order/job id; echoed back in response + stored in our audit log |
| send_email | bool | no | Default true. Set false to suppress notification (you'll email yourself) |
curl -X POST https://getcid.livezentech.com/api/reseller/mint \
-H "Authorization: Bearer YOUR_RESELLER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"quantity": 1,
"customer_email": "[email protected]",
"reference": "wc_order_12345",
"send_email": true
}'
200 OK
{
"ok": true,
"codes": ["AB12CD34EF"],
"code_ids": [4821],
"purchase_id": 1043,
"remaining_quota": 47,
"reference": "wc_order_12345",
"reseller_email": "[email protected]"
}
409 Conflict (quota exhausted)
{
"ok": false,
"code": "insufficient_quota",
"error": "Quota has 3 code(s) left, asked for 10.",
"remaining": 3
}
Error codes
Errors carry code (machine-readable) and error (human-readable). The HTTP status alone is enough for retry logic.
| HTTP | code | When |
|---|---|---|
| 400 | — | Bad request (missing/invalid quantity or email) |
| 401 | missing_token | No Authorization header |
| 401 | invalid_token | Token doesn't match any row |
| 401 | token_revoked | Token's status is revoked |
| 401 | token_expired | Past the expires_at timestamp |
| 403 | ip_not_allowed | Caller IP isn't on the token's allow-list |
| 403 | scope_mismatch | Token is scoped admin but reseller endpoint requires reseller (or vice-versa) |
| 403 | owner_not_reseller | Token owner's account role is not reseller |
| 403 | owner_disabled | Admin disabled the owner account |
| 409 | quota_empty | No codes left at all |
| 409 | insufficient_quota | Asked for more than remaining |
| 409 | race_lost | Concurrent request beat you to the quota; retry once |
| 429 | — | Rate limit exceeded; see X-RateLimit-* headers |
| 500 | — | Server error — codes are not issued; quota is rolled back |
Rate limiting
Per-token hourly limit (default 1000/hr, configurable up to 10000). Response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 982
X-RateLimit-Reset: 1748232000
The reset is a Unix timestamp (start of the next window). On overflow you get 429 with no body; back off until reset.
Iframe embed
Drop the GetCID form into any page on any origin. The iframe content runs on our domain so CSRF + access codes work normally.
| Query param | Values | Effect |
|---|---|---|
| theme | auto, light, dark | Force a colour scheme. Default auto (matches user's OS). |
| accent | indigo, blue, emerald, rose, amber | Primary palette swap (reserved — may not visibly differ today). |
| code | 10-char string | Pre-fill the access code field. Skip the customer-paste step. |
<iframe
src="https://getcid.livezentech.com/embed/getcid?theme=auto"
style="width:100%; min-height:520px; border:0; display:block"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
title="Generate Microsoft Confirmation ID"></iframe>
<script>
window.addEventListener('message', function (e) {
if (e && e.data && e.data.type === 'getcid:resize' && typeof e.data.height === 'number') {
document.querySelectorAll('iframe[src*="embed/getcid"]').forEach(function (f) {
f.style.height = (e.data.height + 20) + 'px';
});
}
});
</script>
The iframe page posts a postMessage with {type: "getcid:resize", height: N} after the result expands. The snippet above includes a listener that resizes the iframe to fit.
WooCommerce hook
The fastest path: hook woocommerce_order_status_completed, mint codes via the API, attach them to the order. Copy-paste-ready PHP lives on your integrations page with your own endpoint URL baked in. Key steps:
- Create a reseller token at /account/api-tokens and copy the plaintext.
- Store it as a WP option, env var, or constant — never in committed code.
- Drop the hook into your theme's
functions.phpor a tiny mu-plugin. - Send
reference: 'wc_order_' . $order_idso refunds/disputes can be traced back to the order.
Stripe webhooks
GetCID handles its own Stripe webhook at https://getcid.livezentech.com/stripe/webhook — you don't need to point Stripe at your own URL to make purchases work. The webhook idempotently mints codes for paid intents (the success-redirect handler also covers this path; whichever fires first wins).
If you also need to react to payment events in your own backend (sync customer records, fire your own emails), use a standalone Stripe webhook — just verify the signature:
<?php
// Verify Stripe webhook signature before trusting the event.
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$secret = 'whsec_...'; // from /admin/settings → Stripe → Webhook signing secret
try {
$event = \Stripe\Webhook::constructEvent($payload, $sigHeader, $secret);
} catch (\Throwable $e) {
http_response_code(400);
exit;
}
if ($event->type === 'payment_intent.succeeded') {
// Codes already minted by GetCID; this is just a sync hook for your own DB.
}
Code samples
PHP (vanilla cURL)
<?php
$ch = curl_init('https://getcid.livezentech.com/api/reseller/mint');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('GETCID_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'quantity' => 1,
'customer_email' => '[email protected]',
'reference' => 'order_42',
'send_email' => true,
]),
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status !== 200 || empty($data['ok'])) {
throw new RuntimeException('Mint failed: ' . ($data['error'] ?? 'HTTP ' . $status));
}
echo 'Issued code: ' . $data['codes'][0];
Node.js (ES modules)
import fetch from 'node-fetch';
const resp = await fetch('https://getcid.livezentech.com/api/reseller/mint', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GETCID_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
quantity: 1,
customer_email: '[email protected]',
reference: 'order_42',
send_email: true,
}),
});
const data = await resp.json();
if (!resp.ok || !data.ok) throw new Error(data.error || 'Mint failed');
console.log('Issued code:', data.codes[0]);