Client API
The client API lives under /api/v1/. All requests must be authenticated with your API key via the X-API-Key header.
X-API-Key: YOUR_API_KEY
Changelog
2026-09-28
- We can now text the check link to your customer for you: pass
sendSms: truewhen creating a check. SMS sending must be enabled for your account first. - New endpoint to resend the link by SMS.
- Create and get responses now include an
smsobject (nullunless we texted the link). resultnow includes the back-of-device assessment:backConditionStatus,backSummary,backCaseOn,backCoverageandbackAccessory.- New
backConditionStatusvaluecaution: a soft pass (see Back of device). If your integration rejects unknown status values, addcautionbefore relying on back results. - New
result.screenProtectorfield, recording what the customer said about a screen protector when screen damage was found. - Webhooks are now documented, including these new fields.
Endpoints
Create a check
POST /api/v1/checks
Creates a new device verification check and returns a link for your customer. Either send the link yourself, or set sendSms: true and we'll text it to them (see Sending the link by SMS). The link expires after 24 hours. Once opened, it can only be reopened within the next 10 minutes (for example, if the customer accidentally closes the page).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerRef |
string | No | Your internal reference (policy number, order ID, etc.) |
make |
string | No | Expected device make (e.g. Apple) |
model |
string | No | Expected device model (e.g. iPhone 14 Pro) |
phoneNumber |
string | No* | Number of the phone being checked. Stored encrypted and never returned via the API. *Required with sendSms, in international format (e.g. +447700900000) |
sendSms |
boolean | No | Text the link to phoneNumber for you. Requires SMS sending to be enabled for your account |
make, model, and phoneNumber are encrypted at rest and used to cross-reference against data captured during the check.
Example request
curl -X POST https://getlustre.co.uk/api/v1/checks \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerRef": "POL-12345",
"make": "Apple",
"model": "iPhone 14 Pro",
"phoneNumber": "+447700900000",
"sendSms": true
}'
Response
{
"checkId": "a1b2c3d4-...",
"verificationUrl": "https://getlustre.co.uk/detect/start?token=abc123...",
"expiresAt": "2024-01-16T10:30:00.000Z",
"sms": {
"status": "sent",
"sentAt": "2024-01-15T10:30:01.000Z",
"sendCount": 1,
"error": null
}
}
Without sendSms, sms is null: send verificationUrl to your customer yourself, by SMS or email. Either way, they must open it on the device being insured.
With sendSms, check sms.status in the response. If the text failed, the check still exists. See If a text fails.
Errors specific to this endpoint
| Status | When |
|---|---|
400 |
sendSms is set but phoneNumber is missing or not in international format |
403 |
sendSms is set but SMS sending isn't enabled for your account |
In both cases no check is created, so it's safe to fix the request and retry.
Resend the link by SMS
POST /api/v1/checks/:checkId/sms
Texts the link again, for example if the customer lost the first message or the number was wrong. Works for checks created without sendSms too, as long as there's a phone number on the check or in the request.
It sends the same link. Resending doesn't create a new link or extend expiresAt.
Request body (optional)
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumber |
string | No | Send to this number instead, in international format. Defaults to the number on the check. The new number replaces the stored one, even if the text then fails |
Example request
curl -X POST https://getlustre.co.uk/api/v1/checks/a1b2c3d4-.../sms \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+447700900001" }'
Response
{
"checkId": "a1b2c3d4-...",
"sms": {
"status": "sent",
"sentAt": "2024-01-15T11:02:00.000Z",
"sendCount": 2,
"error": null
}
}
Errors specific to this endpoint
| Status | When |
|---|---|
400 |
phoneNumber isn't in international format, or there's no number on the check and none was passed |
403 |
SMS sending isn't enabled for your account, or the check belongs to another client |
404 |
Check not found |
409 |
The check is no longer open: it's complete, failed or expired. Create a new check instead |
429 |
The link has already been texted 3 times for this check. See Limits |
A failed text isn't an error response: you get 200 with sms.status: "failed". See If a text fails.
Get a check
GET /api/v1/checks/:checkId
Returns the full result of a check. Poll this after the customer completes the flow, or use it to retrieve results at any time.
Example request
curl https://getlustre.co.uk/api/v1/checks/a1b2c3d4-... \
-H "X-API-Key: YOUR_API_KEY"
Response
{
"checkId": "a1b2c3d4-...",
"customerRef": "POL-12345",
"status": "complete",
"qrVerified": true,
"createdAt": "2024-01-15T10:30:00.000Z",
"openedAt": "2024-01-15T10:35:00.000Z",
"completedAt": "2024-01-15T10:40:00.000Z",
"expiresAt": "2024-01-16T10:30:00.000Z",
"sms": {
"status": "sent",
"sentAt": "2024-01-15T10:30:01.000Z",
"sendCount": 1,
"error": null
},
"device": {
"imei": "353456789012345",
"make": "Apple",
"model": "iPhone 14 Pro",
"lookupBrand": "Apple",
"lookupDevice": "Apple iPhone 14 Pro",
"lookupModel": "A2650"
},
"result": {
"conditionStatus": "pass",
"summary": "No visible screen damage detected.",
"screenProtector": null,
"backConditionStatus": "caution",
"backSummary": "Grip attached, visible panel undamaged",
"backCaseOn": true,
"backCoverage": "partial",
"backAccessory": "grip"
}
}
result is null until the check is complete.
Status values
| Status | Description |
|---|---|
pending |
Link created, not yet opened by customer |
in_progress |
Customer has opened the link |
complete |
Customer completed the check |
failed |
Check failed or was rejected |
expired |
Link expired before the customer completed it |
Screen
result.conditionStatus values
| Value | Description |
|---|---|
pass |
Screen appears undamaged |
damaged |
Screen damage detected |
unclear |
The photo couldn't be assessed (out of frame, too blurry, wrong subject) |
error |
The analysis failed to run |
result.screenProtector values
A cracked screen protector looks just like a cracked screen. When damage is found, the customer is asked whether they have a protector on, and can only clear the damage by removing it and retaking the screen photo.
| Value | Meaning | conditionStatus |
|---|---|---|
null |
No damage found on the first photo, so the customer wasn't asked | as analysed |
none |
Customer confirmed there is no protector | damaged |
removed |
Customer removed the protector and retook the photo. The result reflects the bare screen. | pass or damaged |
not_removed |
Customer says the protector is cracked but couldn't remove it | damaged |
not_removed is recorded as damaged because the screen underneath couldn't be checked. It's a candidate for manual review, since the damage may only be in the protector.
Back of device
result.backConditionStatus values
| Value | Description |
|---|---|
pass |
Back is fully visible and undamaged |
caution |
Soft pass. The customer declared an accessory they can't remove, it covers part of the back, and the visible part is undamaged |
damaged |
Damage detected on the visible part of the back, whether or not an accessory is attached |
unclear |
The back couldn't be assessed: poor photo, or a declared accessory covers the whole back — review recommended |
error |
The analysis failed to run |
null |
No back result recorded |
result.backCoverage values: how much of the back the AI found covered
| Value | Meaning |
|---|---|
none |
Bare back panel fully visible |
partial |
Something covers part of the back (e.g. a grip, ring or sticker) and the rest is visible |
full |
A case, wallet or full skin hides most or all of the back |
null |
Not recorded (checks completed before 2026-09-28) |
backCaseOn is true whenever backCoverage is not none. It's kept for compatibility; prefer backCoverage.
result.backAccessory values: what the customer declared they can't remove
| Value | Meaning |
|---|---|
grip |
Phone grip, ring or stand |
skin |
Stuck-on skin or sticker |
case |
Case that won't come off |
other |
Something else |
null |
Nothing declared |
Customers are asked to remove any case or accessory first and can only continue with one attached by declaring it. An undeclared case can't be submitted.
An accessory can hide damage underneath it. Treat
cautionand a declaredbackAccessoryas a factor in your decision rather than an equivalent ofpass.
Reading the results together
| Outcome | Screen | Back | Suggested handling |
|---|---|---|---|
| Clear pass | pass |
pass |
Accept |
| Pass after protector removed | pass + screenProtector: "removed" |
any | Treat the screen as a pass |
| Soft pass | pass |
caution |
Accept with caution, e.g. note or exclude the covered area |
| Review | unclear, or damaged + screenProtector: "not_removed" |
or unclear |
Manual review. Either side on its own is enough |
| Damaged | damaged + screenProtector none or removed |
or damaged |
Decline or apply your damage process. Either side on its own is enough |
sms: null unless we've texted the link for this check. See The sms object.
device fields
| Field | Source |
|---|---|
imei |
Captured from device during check |
make / model |
Provided by you when creating the check (decrypted) |
lookupBrand / lookupDevice / lookupModel |
IMEI.info lookup result — independent verification of the device |
phoneNumberis stored encrypted and is never returned via the API.
Sending the link by SMS
Instead of sending the verification link yourself, you can have us text it to your customer. Pass sendSms: true when you create a check, or call Resend the link by SMS later.
Before you start
SMS sending is off by default and has to be enabled for your account. Contact us to switch it on. Until then, any request using SMS returns 403.
Which number to use
Use the number of the phone being checked. The check only works when the link is opened on that device, so a link sent to a different phone (a partner's, a work phone) can't be completed there.
Numbers must be in international (E.164) format: +, country code, then the number, with no spaces or leading zero.
| ✅ Valid | ❌ Invalid |
|---|---|
+447700900000 |
07700900000 (no country code) |
+14155552671 |
+44 7700 900000 (spaces) |
What the customer receives
The text comes from our sender ID and starts with your account name, so the customer knows who it's from:
Acme Insurance: please complete your device check. Open this link on the phone you're checking: https://getlustre.co.uk/detect/start?token=…
Your account name is the name you were set up with. Contact us if it should read differently in texts.
The sms object
Returned by Create a check, Resend the link by SMS and Get a check. It's null if we've never texted the link for the check.
| Field | Description |
|---|---|
status |
Outcome of the most recent attempt. sent: accepted by our SMS provider for delivery. failed: couldn't be sent, see error |
sentAt |
When the link was last texted successfully |
sendCount |
How many times the link has been texted successfully. Failed attempts aren't counted |
error |
Why the most recent attempt failed, e.g. an invalid number. null after a successful send |
A failed resend doesn't erase an earlier success. For example, after one successful text and one failed resend you'd see status: "failed", sendCount: 1, and sentAt from the first text.
sentmeans our SMS provider accepted the message, not that it reached the phone. Carriers can still fail to deliver it, for example if the phone is off for a long time. If the customer hasn't opened the link (the check is stillpending), resend it or contact them another way.
If a text fails
The check is still created, and verificationUrl is still valid. You can:
- Fix the number and resend by passing a corrected
phoneNumberto Resend the link by SMS, or - Send
verificationUrlyourself, by your own SMS or email.
Limits
- 3 successful texts per check, including the first. Failed attempts don't count. After that, resending returns
429. SendverificationUrlyourself, or create a new check. - Only open checks. The link can only be texted while the check is
pendingorin_progressand beforeexpiresAt. Resending never extends the expiry.
SMS and webhooks
Webhooks fire when a check is completed and don't include SMS status. Use Get a check to see the sms object.
Webhooks
If a webhook URL is configured for your account, we POST to it as soon as a customer completes a check.
{
"checkId": "a1b2c3d4-...",
"customerRef": "POL-12345",
"status": "complete",
"imei": "353456789012345",
"qrVerified": true,
"conditionStatus": "pass",
"summary": "No visible screen damage detected.",
"screenProtector": null,
"backConditionStatus": "caution",
"backSummary": "Grip attached, visible panel undamaged",
"backCaseOn": true,
"backCoverage": "partial",
"backAccessory": "grip"
}
Field values match the result fields of Get a check.
- Delivery is attempted once, with no retries, so don't rely on the webhook alone.
- Requests aren't signed. Treat the webhook as a notification only and call
GET /api/v1/checks/:checkIdfor the authoritative result. - Respond with any
2xxstatus. The response body is ignored.
Errors
| Status | Meaning |
|---|---|
400 |
Missing or invalid request body (including a missing or badly formatted phoneNumber with sendSms) |
401 |
Missing or invalid API key |
403 |
Check belongs to a different client, or SMS sending isn't enabled for your account |
404 |
Check not found |
409 |
Check is no longer open (resending SMS) |
429 |
SMS resend limit reached for this check |
500 |
Server error |
Typical integration flow
- Customer purchases insurance and provides their IMEI, make, model, and phone number
- Your system calls
POST /api/v1/checkswith that data - You send the returned
verificationUrlto the customer via SMS or email, or passsendSms: truein step 2 and we text it to them - Customer opens the link on their device and completes the check (~2 minutes)
- You receive a webhook (if configured) and/or call
GET /api/v1/checks/:checkIdto retrieve the result - Cross-reference
device.make/device.modelagainstdevice.lookupBrand/device.lookupDevice, and useresult.conditionStatusandresult.backConditionStatus(see Reading the results together) to make your underwriting decision