Documentation Index
Fetch the complete documentation index at: https://docs.payments.sardine.ai/llms.txt
Use this file to discover all available pages before exploring further.
Authentication
All Identity API endpoints use HTTP Basic Auth. Pass your clientId as the username and clientSecret as the password on every request.
curl https://api.sandbox.sardine.ai/v1/identity/... \
-u "$CLIENT_ID:$CLIENT_SECRET"
Never make these calls from a browser or mobile client. Your clientSecret must remain server-side only.
Step 1 — Check if the customer exists
Before creating a new customer, check whether one already exists for the user’s phone number to avoid duplicate records.
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities/search \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+14155551234" }'
If customerId is returned, the customer already exists — skip to Step 3. If the response is empty, proceed to Step 2.
Step 2 — Create the customer
Register the user with their phone number. The customerId returned here is your durable reference to this user in all subsequent API calls.
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+14155551234" }'
{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"createdAt": "2026-06-01T10:00:00Z"
}
Store customerId against your user record in your own database.
Call this endpoint from your backend to get a hosted widget URL for the user. Choose the right flow and scope for your use case.
curl -X POST https://api.sandbox.sardine.ai/v1/identity/consents/widget \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"successUrl": "https://yourapp.com/kyc/success",
"manualKycUrl": "https://yourapp.com/kyc/manual",
"scope": ["profile", "doc_kyc"],
"flow": "kyc_input"
}'
{
"widgetUrl": "https://identity.sardine.ai/?client_token=abc123&consent_id=xyz789&success_url=..."
}
Choosing a flow
| Flow | When to use |
|---|
kyc_input | New users, or when you need the user to verify additional scopes not yet on file |
kyc_sharing | User has already been verified by another Sardine partner and you want them to consent to share that identity with you |
Choosing scopes
| Scope | What it collects |
|---|
profile | Name, date of birth, address, email, phone number |
doc_kyc | Government ID scan (front + back) and biometric liveness check |
liveness | Liveness check only |
ssn | Social Security Number (US users, when required) |
Pass multiple scopes together: ["profile", "doc_kyc"] is the standard full-KYC combination.
manualKycUrl
Provide a manualKycUrl as a fallback destination if the user cannot be verified automatically (e.g. document scan quality is too low). This is optional but recommended for production.
Step 4 — Redirect the user
Return the widgetUrl to your frontend and redirect the user to it, or embed it in an iframe.
// Server response to your frontend
res.json({ widgetUrl: data.widgetUrl });
// Frontend redirect
window.location.href = widgetUrl;
The widget handles all verification steps. When the user completes (or abandons) the flow, they are redirected to your successUrl.
Step 5 — Retrieve the verified identity
After the user returns to your successUrl, call this endpoint from your backend to retrieve the verified data.
curl https://api.sandbox.sardine.ai/v1/identity/entities/3f8c1a22-1234-4abc-9def-000000000001 \
-u "$CLIENT_ID:$CLIENT_SECRET"
Response
{
"profile": {
"userId": "3f8c1a22-1234-4abc-9def-000000000001",
"clientId": "your-client-id",
"consentId": "b1c2d3e4-5678-4abc-9def-000000000002",
"consentedAt": "2026-06-01T10:30:00Z",
"revokedAt": null,
"primaryIdentity": true,
"fullName": "Jane Smith",
"dateOfBirth": "1990-06-15",
"emailAddress": "jane@example.com",
"phoneNumber": "+14155551234",
"address": {
"street": "123 Main St",
"city": "San Francisco",
"region": "CA",
"postalCode": "94105"
}
},
"documentData": {
"documentType": "DRIVERS_LICENSE",
"documentNumber": "D1234567",
"dateOfBirth": "1990-06-15",
"expiryDate": "2028-06-15",
"issuingCountry": "US",
"firstName": "Jane",
"lastName": "Smith"
},
"documentKyc": {
"front": "<base64-encoded image>",
"back": "<base64-encoded image>",
"selfie": "<base64-encoded image>"
}
}
What’s included per scope
| Field | Requires scope |
|---|
profile | profile |
documentData | doc_kyc |
documentKyc (images) | doc_kyc |
Fields outside the consented scopes are returned as null.
Consent states
If the identity belongs to another client (i.e. kyc_sharing flow), the API enforces consent state:
| State | Behaviour |
|---|
| Consent not found | 400 Consent not found |
| Consent pending (user has not approved yet) | 400 Pending user consent |
| Consent revoked | 400 Consent has been revoked |
| Consent active | 200 with full data |
Error reference
| Status | Message | Resolution |
|---|
400 | Phone number is required | Include phoneNumber in the request body |
400 | Customer already exists | Use the existing customerId from /identity/entities/search |
400 | Customer ID is required | Include customerId in the widget request |
400 | Customer not found for customer ID | Verify the customerId was created by this client |
400 | Invalid scope | Use one of: profile, doc_kyc, liveness, ssn |
400 | User already consented to this flow | The user has already completed kyc_input for this client |
401 | Unauthorized | Check that clientId and clientSecret are correct and being sent as Basic Auth |
Go to production
- Test the full flow end-to-end in sandbox.
- Confirm identity data is retrieved correctly after widget completion.
- Contact your Sardine integration contact to complete the review.
- Swap
api.sandbox.sardine.ai → api.sardine.ai and replace with production credentials.