Developers

API documentation

Verify identities, manage plans and credit, and receive results on signed webhooks, all from your own backend. Everything speaks JSON.

Introduction

The Sankofa IDme API lets you create and track identity verifications, manage your plan, and check your balance. The base URL is https://idme-api.sankofa.dev/v1. Amounts are whole credits; one credit is one verification.

Authentication

Create a secret key in the dashboard under Developers. Send it as a bearer token on every request, and keep it on your server, never in a browser. The secret is shown once and stored hashed, so nobody can recover it.

https://idme-api.sankofa.dev/v1

Authorization: Bearer sk_...     # every request

Plans

List the plans available to your account. Prices are quoted in your account currency.

GET /v1/plans
→ {
  "plans": [
    { "id": "9c2f…", "name": "Growth", "kind": "subscription",
      "monthlyAllowance": 500, "termMonths": 12,
      "priceMicro": 420000000, "active": true }
  ],
  "currency": "GHS",
  "rateMicro": 15500000
}

priceMicro is the price in USD micro-units (a subscription quotes the full term). rateMicro converts USD to your account currency, so a client can show a local price.

Purchase a plan

Start a purchase with a plan id. You get back a hosted checkout link. Hand it to whoever pays, then poll the reference until the payment settles. Credit is applied server side once payment is confirmed, so never grant anything client side.

curl -X POST https://idme-api.sankofa.dev/v1/payments \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "planId": "9c2f…" }'

→ { "reference": "idme_…", "credits": 500,
    "authorizationUrl": "https://checkout.paystack.com/…" }

# Hand authorizationUrl to whoever pays, then poll the reference:
GET /v1/payments/idme_…
→ { "payment": { "reference": "idme_…", "status": "success", "credits": 500 },
    "balance": 512 }

The reference is the idempotency handle: one checkout per reference, no matter how often you poll. status moves from pending to success once the payment clears.

Available credit

Check your balance any time. Spendable credit is the sum of your subscription allowance (which resets each month) and your permanent purchased credit.

GET /v1/wallet
→ {
  "balance": 512,
  "allowance": 500,
  "purchased": 12,
  "freeRemaining": 8,
  "currency": "GHS",
  "rateMicro": 15500000,
  "subscription": { "monthlyAllowance": 500, "periodEnd": "2026-10-06T00:00:00Z" }
}

Create a verification

Start a check. You get back a hosted URL and a QR image your customer opens on their phone. All fields are optional. Pass an Idempotency-Key so a retry never creates a second check.

curl -X POST https://idme-api.sankofa.dev/v1/verifications \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_8412" \
  -d '{ "reference": "order_8412",
        "redirect_url": "https://acme.gh/verified" }'

→ 201 {
  "id": "ver_3nK9…",
  "token": "3nK9…",
  "status": "pending",
  "reference": "order_8412",
  "verifyUrl": "https://idme.sankofa.dev/v/3nK9…",
  "qr": "data:image/png;base64,…",
  "expiresAt": "2026-09-07T14:00:00Z"
}

The check reserves one credit when it starts and expires after 24 hours if the customer does not finish. You can also fetch the QR as a PNG at GET /v1/verifications/{id}/qr.png.

Verification status

Read a verification any time for its current decision and scores. If a webhook was missed, call refresh to pull the latest decision from the verification engine.

GET /v1/verifications/ver_3nK9…
→ {
  "id": "ver_3nK9…",
  "status": "approved",
  "reference": "order_8412",
  "decision": { "face_match": 0.986, "document": "authentic" },
  "completedAt": "2026-09-06T14:03:12Z"
}

# Force a re-check against the engine (webhook fallback):
POST /v1/verifications/ver_3nK9…/refresh

status is one of pending, approved, declined, in_review, abandoned or expired.

List verifications

Page through your verifications, newest first. Filter by status.

GET /v1/verifications?status=approved&limit=50&offset=0
→ { "verifications": [
    { "id": "ver_3nK9…", "reference": "order_8412",
      "status": "approved", "createdAt": "…", "completedAt": "…" }
] }

limit defaults to 50, up to 200. offset defaults to 0.

Webhooks

Add an endpoint in the dashboard and we post an event to it whenever a verification finishes. Verify the signature header before you trust the body. It is an HMAC SHA256 of the raw body, keyed with your endpoint secret.

X-Sankofa-Signature: sha256=<hex>
X-Sankofa-Event: verification.approved
X-Sankofa-Delivery: <delivery uuid>

{
  "id": "evt_…",
  "type": "verification.approved",
  "createdAt": "2026-09-06T14:03:12Z",
  "data": {
    "id": "ver_3nK9…",
    "status": "approved",
    "reference": "order_8412",
    "decision": { "face_match": 0.986 }
  }
}

Event types are verification.approved, verification.declined, verification.in_review, verification.abandoned and verification.expired.

Idempotency

Send an Idempotency-Key on POST /v1/verifications so a retry after a dropped connection never creates a second check or a second charge. Reuse the same key to get the first response back, marked with Idempotent-Replay: true. The same key with a different body returns 409.

Rate limits

API keys are limited to about 10 requests per second with a short burst above that. Over the limit returns 429. Ask us to raise the limit for a high volume key.

Errors

The API uses standard HTTP status codes. Bodies carry a short message.

StatusMeaning
200 / 201Success
400Invalid request body
401Missing or bad API key
402Out of balance, top up a plan
404Verification not found
409Idempotency key reused with a different body
429Rate limit reached