Get a key
API access is part of every dealer plan: Verified Dealer, Dealer Pro and Dealer Enterprise.
- 1In your dealer dashboard, open API and make a key. On Dealer Enterprise, each key belongs to a branch.
- 2Copy it straight away: we show it once.
- 3Keep it on your server, never in an app or a web page. If it leaks, revoke it and make a new one.
Send it in the Authorization header of every request:
Authorization: Bearer sk_live_…Check a device
POST/v1/checks
Checks one device and bills it like a check from your dashboard.
Send the IMEI, or a serial number as query, as JSON with your key in the Authorization header.
curl -X POST https://cdr-server.vercel.app/v1/checks \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"imei": "354398601234891"}'What to send
| Field |
|---|
imeistringThe IMEI to check. |
querystringA serial number, in place of imei. Send one of the two, 4 to 40 characters long. |
The answer
{
"id": "6720c1a3e4b0a1f2c3d4e5f6",
"reference": "SPV-8B21D4E911C0A7F3B2D1",
"checkedAt": "2026-09-28T07:42:10.000Z",
"query": "354398601234891",
"imeiValidity": "valid",
"device": { "manufacturer": "Apple", "model": "iPhone 13 Pro" },
"outcome": "CLEAN",
"result": "No adverse record",
"riskLevel": "low",
"findings": [
{ "tone": "ok", "headline": "No lost or stolen report against this device", "detail": "…" }
],
"billing": {
"included": false,
"charged": 20,
"currency": "KES",
"creditBalance": 380,
"includedLeft": 0
},
"disclaimer": "…"
}| Field |
|---|
idstringThe check's id. |
referencestringSimuplace's reference for this check. |
checkedAtstringWhen it ran, as an ISO 8601 time. |
querystringThe number as checked. |
imeiValiditystringvalid, invalid (the number was never issued), or not_applicable for serial numbers. |
deviceobject | nullThe maker and model, when we can tell. Either can be null. |
outcomestringWhat our records say. One of the outcomes below. |
resultstringThe same, in words, as your dashboard shows it. An invalid IMEI reads “Invalid IMEI”. |
riskLevelstring | nulllow, medium or high, with the reasons in findings. |
findingsarrayWhat the rating rests on. Each has a tone (ok, info, warn or bad), a headline and a detail. |
billingobjectWhether it came out of your included checks, what it cost, and what's left. |
disclaimerstringWhat a result can and can't prove, to show with it. |
Outcomes
| outcome |
|---|
CLEAN“No adverse record”Registered on Simuplace, with no lost or stolen report. |
NOT_FOUND“Not registered”Not registered on Simuplace. We hold nothing against it. |
LOST“Reported lost”Its owner reported it lost. |
STOLEN“Reported stolen”Its owner reported it stolen. |
DISPUTED“Ownership disputed”Reported stolen, and a claim over who owns it is open. |
CAUTION“Disputed history”A past dispute over it was resolved; the history stands. |
See your usage
GET/v1/usage
This month's included checks, paid checks and credit, for your own systems.
curl https://cdr-server.vercel.app/v1/usage \
-H "Authorization: Bearer sk_live_…"{
"planActive": true,
"paidThrough": "2026-10-28T07:00:00.000Z",
"month": { "start": "2026-09-28T07:00:00.000Z", "end": "2026-10-28T07:00:00.000Z" },
"plan": "verified",
"includedChecks": 25,
"includedUsed": 25,
"paidChecks": 41,
"nextCheckPrice": 20,
"creditBalance": 380,
"currency": "KES"
}| Field |
|---|
planActivebooleanWhether your plan is paid up. |
paidThroughstring | nullThe day your plan is paid up to. |
monthobject | nullThis plan month's start and end. null while the plan isn't active. |
planstringverified, pro or enterprise. |
includedChecksnumberThe checks your plan includes each month. |
includedUsednumberIncluded checks used this month, across the dashboard, bulk uploads and the API. |
paidChecksnumberChecks paid from credit this month, one at a time from the dashboard or the API. |
nextCheckPricenumberWhat the next paid check costs, in KES. |
creditBalancenumberYour prepaid credit, in KES. |
currencystringAlways KES. |
A test key answers with zeros, and with paidThrough and month set to null.
Test keys
To build against the API safely, make a test key under API in your dealer dashboard, on any plan. It starts sk_test_ and works like a live key, but every check answers with a sample result in the same shape: no device is looked up, no record is read, and nothing is charged or counted. /v1/usage answers with zeros.
The IMEI's last digit picks the answer, so you can try each one:
| Last digit | Try |
|---|---|
| 2NOT_FOUND | 354398601234842 |
| 3LOST | 354398601234883 |
| 4STOLEN | 354398601234834 |
| 5DISPUTED | 354398601234875 |
| 6CAUTION | 354398601234826 |
| Any otherCLEAN | 354398601234891 |
| Wrong check digitInvalid IMEI | 354398601234892 |
A sample's reference starts SPV-00000000, and its billing is all zeros. Revoke a test key like any other.
Billing
Each month of your plan includes 25 checks on Verified Dealer, or 150 on Dealer Pro, shared across the dashboard, bulk uploads and the API. After that, each API check comes out of your prepaid credit at the price for how many you've paid for this month:
| Checks in a month | Per check |
|---|---|
| 1 – 499 | KES 20 |
| 500 – 4,999 | KES 15 |
| 5,000 – 24,999 | KES 10 |
| 25,000 – 99,999 | KES 7 |
| 100,000+ | Let's talk |
On Dealer Enterprise, your contract sets the included checks and the price of each check after them. Top up credit by M-Pesa from your dashboard. A check that can't be run isn't charged.
Limits
- Up to 120 requests a minute per key, checks and usage together.
- Each answer to a working key says what's left in its
RateLimitheader;resetis the seconds until the minute starts over. A request turned away for its key doesn't count. - Over the limit, you get 429 rate_limited with a
Retry-Afterheader: the seconds to wait before sending again. - One device per request: an
imei, or a serial number asquery, 4 to 40 characters long. For a whole file of devices, use Bulk uploads in your dealer dashboard.
RateLimit-Policy: 120;w=60
RateLimit: limit=120, remaining=97, reset=41Errors
Every error is JSON: a message you can show people, and a content code your system can act on (null where there's no code).
{
"message": "You've used this month's 25 included checks, and your credit is too low for another (KES 20). Top up to keep checking.",
"content": "insufficient_credit"
}- 400
- No code
- The body has no imei (or query), or it's shorter than 4 or longer than 40 characters.
- invalid
- Insurer keys only: the number isn't a valid 15-digit IMEI. It isn't charged.
- 401
- unauthorized
- No key, a wrong key, or a revoked key.
- 402
- plan_inactive
- Your plan isn't paid up for this month.
- insufficient_credit
- The included checks are used and your credit is too low for the next one.
- 403
- key_paused
- The key is for a branch that isn't on your plan now. Use another key.
- api_closed
- Insurer keys only: API access is off for your insurer.
- 429
- rate_limited
- More than 120 requests in a minute on one key. Wait the seconds in Retry-After.
- 500
- No code
- Something went wrong on our side. A check that can't be run isn't charged, so try again.
For insurers
Insurers use the same API. Your main contact makes keys under Device checks in your insurer portal, once Simuplace's team turns API access on. Each phone is billed at your price on your monthly invoice, not from credit.
Send IMEIs only: a number that isn't a valid IMEI comes back as 400 invalid and isn't charged. Each answer also carries the phone's record:
{
…
"record": {
"registeredSince": "2026-07-01T09:12:00.000Z",
"ownerChanges": 1,
"earlierClaims": 0,
"checkedByOthers": 2
},
"billing": { "charged": 20, "currency": "KES", "invoice": "2026-10" }
}| Field |
|---|
registeredSincestring | nullWhen its owner got it, or null if it isn't registered on Simuplace. |
ownerChangesnumberHow many times it changed hands on Simuplace. |
earlierClaimsnumberClaims on it under any policy: how many, never whose. |
checkedByOthersnumberChecks by anyone but you and its owner in the last 30 days. Never who. |
billingobjectWhat it cost, and the month whose invoice it's on. |
/v1/usage answers with this month so far. A key stops working with 403 api_closed while API access is off.
{
"month": { "start": "2026-09-30T21:00:00.000Z", "end": "2026-10-31T21:00:00.000Z" },
"checks": 1284,
"amount": 25680,
"price": 20,
"currency": "KES",
"invoiceOn": "2026-10-31T21:00:00.000Z"
}| Field |
|---|
monthobjectThis month's start and end, in Nairobi time. |
checksnumberPhones checked this month. |
amountnumberWhat they cost so far, in KES. |
pricenumberYour price for each phone, in KES. |
invoiceOnstringWhen this month's invoice is made: the 1st of next month. |
Results reflect Simuplace's records at the moment of the check. “No adverse record” means none was identified in the data sources checked; it is not a guarantee, and not proof of ownership.
Stuck? Ask us from Help in your dealer dashboard.
