> ## 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.

# API Integration

> Full implementation reference for the Sardine Universal Identity API

## Authentication

All Identity API endpoints use HTTP Basic Auth. Pass your `clientId` as the username and `clientSecret` as the password on every request.

```bash theme={null}
curl https://api.sandbox.sardine.ai/v1/identity/... \
  -u "$CLIENT_ID:$CLIENT_SECRET"
```

<Warning>
  Never make these calls from a browser or mobile client. Your `clientSecret` must remain server-side only.
</Warning>

***

## 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.

```bash theme={null}
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](#step-3--generate-a-widget-url). 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.

```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "phoneNumber": "+14155551234" }'
```

```json theme={null}
{
  "customerId": "3f8c1a22-1234-4abc-9def-000000000001",
  "createdAt": "2026-06-01T10:00:00Z"
}
```

Store `customerId` against your user record in your own database.

***

## Step 3 — Generate a widget URL

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.

```bash theme={null}
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"
  }'
```

```json theme={null}
{
  "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.

```js theme={null}
// 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.

```bash theme={null}
curl https://api.sandbox.sardine.ai/v1/identity/entities/3f8c1a22-1234-4abc-9def-000000000001 \
  -u "$CLIENT_ID:$CLIENT_SECRET"
```

### Response

```json theme={null}
{
  "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

1. Test the full flow end-to-end in sandbox.
2. Confirm identity data is retrieved correctly after widget completion.
3. Contact your Sardine integration contact to complete the review.
4. Swap `api.sandbox.sardine.ai` → `api.sardine.ai` and replace with production credentials.
