Challenges
Create a challenge
POST https://api.xipid.org/challenges
Asks a person the brand linked, named by the pairwise identifier their referral answered, to approve a request. XipID texts them a link before it answers, and posts the outcome to the callback URL when the challenge closes. The callback URL is https and resolves to public addresses alone, or the challenge is refused before anything is made; XipID checks it again at each delivery and never follows a redirect. A retry under the same Idempotency-Key is answered the same challenge, its link null: the link is shown once. An agent of the brand's org creates one for a person who delegated their presence to it, naming them by the pairwise identifier one of its org's brands holds for them: the challenge goes out under that brand, by the brand's text, and its outcome, signed with the brand's secrets, names the agent. Without a live delegation it is refused, consent_missing, before anything is sent.
Scope: any live key of the brand, or any live credential of an agent.
Limits: 1,000 requests a minute per key and 2,000 requests a minute per source address; every answer says what is left in X-RateLimit-Remaining, and a refusal when to come back in Retry-After. Each challenge is a text: at most 5 an hour and 20 a day reach one number whoever asks, and one org's brands ask for at most 600 an hour and 10,000 a day; past either, the create is refused 429 and makes nothing.
Idempotency: takes an Idempotency-Key, which it requires: a retry under the same key is answered what the first request was, for 24 hours, and the key sent with another request is refused.
Changes: 2026-09-26: added.
Parameters
Idempotency-KeyThe operation's key, 1 to 255 characters, best a UUID, in double quotes as a structured field string: a retry of the operation sends it again, and is answered what the first request was, for 24 hours.
Xip-VersionThe version to answer in; when absent, the version the brand's org is pinned to.
2026-09-26: The first version: challenges, subjects and events, and the signed outcome.
Body
subjectpairwise_id
requesttypeWhat the brand asks the person to approve.
wire: A wire transfer.beneficiary_change: A change to whom a payment goes.withdrawal: A withdrawal of funds.password_reset: A reset of a password.address_change: A change of address.other: Anything else; the title says what.
titlesummarymoneyamount_minorcurrency
counterpartyreferencerequested_at
policychecksliveness: The selfie is of a live person at the phone, not a photo or a recording.face_match: The selfie's face matches the person's reference face.
allow_declineallow_noteexpires_in_seconds
callbackurl
metadata
{
"subject": {
"pairwise_id": "Vb3kQ9xR2mTz7LpW4nYc8d"
},
"request": {
"type": "wire",
"title": "Wire to Ridgeline Fabrication",
"summary": "The first payment on invoice 2291.",
"money": {
"amount_minor": 4500000,
"currency": "USD"
},
"counterparty": "Ridgeline Fabrication",
"reference": "INV-2291",
"requested_at": "2026-11-19T13:04:00-05:00"
},
"policy": {
"checks": [
"liveness",
"face_match"
],
"expires_in_seconds": 600
},
"callback": {
"url": "https://acme-bank.example/xipid/outcomes"
},
"metadata": {
"branch": "downtown"
}
}
Answers
201 Created
The challenge, with the link the person was texted, which no other answer shows. Location names it.
Its headers: X-Request-Id, Xip-Version, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Location.
idbrand_idstatusWhere the challenge is in its life; the last five are final.
created: Made; the text with its link is not sent yet.sent: The text with the link went to the person's phone.opened: The person opened the link and is reading the request.approving: The person approved, and the checks the policy asks for are running.verified: The person approved and every check passed. Final.check_failed: A check the policy asks for did not pass. Final.declined: The person declined. Final.expired: No decision came beforeexpires_at. Final.canceled: The brand canceled it, or its text could not be sent. Final.
subjectidpairwise_idreferencetierHow far the person's identity is verified.
0: Their phone and email are verified.1: A selfie too, which keeps their reference face.2: A government document too.
requesttypeWhat the brand asks the person to approve.
wire: A wire transfer.beneficiary_change: A change to whom a payment goes.withdrawal: A withdrawal of funds.password_reset: A reset of a password.address_change: A change of address.other: Anything else; the title says what.
titlesummarymoneyamount_minorcurrency
counterpartyreferencerequested_at
policychecksliveness: The selfie is of a live person at the phone, not a photo or a recording.face_match: The selfie's face matches the person's reference face.
allow_declineallow_noteexpires_in_seconds
callbackurl
metadataagentidkeyversionnameorgname
credential_id
checkscheckA check the page runs before an approval counts.
liveness: The selfie is of a live person at the phone, not a photo or a recording.face_match: The selfie's face matches the person's reference face.
passedscoremodeThe liveness mode the vendor says ran.
movement_and_light: Colored lights flashed on the screen during the selfie.movement: No lights, for a device that asks for reduced motion; the vendor rates it the less accurate.
checked_at
checkA check the page runs before an approval counts.
liveness: The selfie is of a live person at the phone, not a photo or a recording.face_match: The selfie's face matches the person's reference face.
passedscoremodeThe liveness mode the vendor says ran.
movement_and_light: Colored lights flashed on the screen during the selfie.movement: No lights, for a device that asks for reduced motion; the vendor rates it the less accurate.
reasonWhy the check did not pass.
below_threshold: It scored under the threshold XipID holds it to.no_face: The vendor found no face to score.capture_failed: The selfie did not complete.reference_missing: The brand deleted the reference face while the challenge was open.
checked_at
consentagreed_atnotice_versionThe notice the person agreed to before the selfie, by its version.
request-selfie-2026-11-02: A request's selfie notice of November 2, 2026, for a brand that keeps no frames.person-selfie-2026-11-02: The notice of November 2, 2026, before the selfie that takes a person to Tier 1.request-selfie-frames-2026-11-02: A request's selfie notice of November 2, 2026, for a brand that keeps frames, which says so and that the brand may get them only if the request is disputed.
notetextprovider_message_idstatusWhat became of the text, as the channel reports it; the last three are final.
accepted: The provider took the text.queued: The provider holds the text before the carrier has it.sending: The text is on its way to the carrier.sent: The carrier has the text.delivered: The phone received the text. Final.undelivered: The carrier could not deliver the text. Final.failed: The provider could not send the text. Final.
error_code
created_atsent_atopened_atdecided_atexpires_atlink
{
"id": "01JD2R5W8Y3K6M9P2S5V8X1Z4B",
"brand_id": "01JD2R1A3C5E7G9J1K3M5N7P9Q",
"status": "sent",
"subject": {
"id": "01JD2R2B4D6F8H0J2K4M6N8P0Q",
"pairwise_id": "Vb3kQ9xR2mTz7LpW4nYc8d",
"reference": "acct_4417"
},
"request": {
"type": "wire",
"title": "Wire to Ridgeline Fabrication",
"summary": "The first payment on invoice 2291.",
"money": {
"amount_minor": 4500000,
"currency": "USD"
},
"counterparty": "Ridgeline Fabrication",
"reference": "INV-2291",
"requested_at": "2026-11-19T18:04:00.000Z"
},
"policy": {
"checks": [
"liveness",
"face_match"
],
"allow_decline": true,
"allow_note": true,
"expires_in_seconds": 600
},
"callback": {
"url": "https://acme-bank.example/xipid/outcomes"
},
"metadata": {
"branch": "downtown"
},
"checks": [],
"text": {
"status": "sent"
},
"created_at": "2026-11-19T18:04:00.512Z",
"sent_at": "2026-11-19T18:04:01.204Z",
"expires_at": "2026-11-19T18:14:00.512Z",
"link": "https://app.xipid.org/c/Ab3dEf6hJk9mNp2qRs5tUv"
}
Errors
json_invalid, 400: The request body is not valid JSON.parameter_missing, 400: The request lacks a required parameter.parameter_invalid, 400: The request contains invalid parameters.parameter_unknown, 400: The request contains a parameter this route does not take.idempotency_key_missing, 400: This route requires an Idempotency-Key header.version_invalid, 400: Xip-Version names no released version of this API.authentication_required, 401: Authentication is required.authentication_failed, 401: The credential is invalid, expired or revoked.idempotency_key_in_use, 409: The first request with this Idempotency-Key is still running.content_too_large, 413: The request body is larger than this route accepts.media_type_unsupported, 415: This route does not accept that Content-Type.idempotency_key_reused, 422: This Idempotency-Key was used for a different request.subject_unlinked, 422: The pairwise identifier names no person linked to this brand, or to a brand of this agent's org.consent_missing, 422: The person has not granted this brand, or this agent, their presence.channel_unavailable, 422: The person has no phone XipID may text.reference_face_missing, 422: The person has no face on file.callback_url_refused, 422: No outcome can be posted to that callback URL.text_failed, 422: The provider refused the text.rate_limit_exceeded, 429: Too many requests; try again later.internal_error, 500: Something went wrong on our side.upstream_error, 502: A service XipID depends on failed.service_unavailable, 503: The service is temporarily unavailable.upstream_timeout, 504: A service XipID depends on did not answer in time.