Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions mintlify/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions mintlify/platform-overview/core-concepts/account-model.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,63 @@ PENDING → ACTIVE

- **PENDING**: Created, undergoing verification/screening
- **ACTIVE**: Verified, ready for transactions
- **PENDING_OWNERSHIP_VERIFICATION**: The customer must prove they control this self-custody wallet
- **UNVERIFIED**: The last ownership attempt failed. Start a new challenge to retry
- **INACTIVE**: Disabled (can be reactivated)
- **UNDER_REVIEW**: Additional review required

You'll receive `INTERNAL_ACCOUNT.BALANCE_UPDATED` webhooks as balance changes, and `INTERNAL_ACCOUNT.STATUS_UPDATED` webhooks when account status changes (e.g., ACTIVE to FROZEN).

### Ownership Verification

Some regions require the owner of a self-custody wallet to prove they control it (e.g. the EU). Register the wallet with `ownershipType` set to `FIRST_PARTY` and it starts at `PENDING_OWNERSHIP_VERIFICATION`.

The status is not a block: transfers below the regulatory threshold go through while a wallet is pending. Larger transfers need verification to be complete first.

Start a challenge on the account, choosing how the owner will prove control:

```bash
curl -X POST https://api.lightspark.com/grid/2025-10-13/customers/external-accounts/ExternalAccount:abc123/challenge \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"method": "WALLET_SIGNATURE"}'
```

**`WALLET_SIGNATURE`** returns a message for the wallet to sign:

```json
{
"method": "WALLET_SIGNATURE",
"messageToSign": "I am verifying ownership of the wallet address 7Ub1FK9aE5MsQBYfEdxSXorWeCGq9xPgPygL9XjA3KZz as a361e175-69b4-4bfb-9ceb-acb6ea672cf0. This message was signed on 04/09/2026 to confirm my control over this wallet.",
"expiresAt": "2026-09-06T00:00:00Z"
}
```

Submit the signature to complete verification synchronously. The response is the external account, with `status` set to `ACTIVE` on success:

```bash
curl -X POST https://api.lightspark.com/grid/2025-10-13/customers/external-accounts/ExternalAccount:abc123/verify \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"signature": "5tCtPdm7CnT3YyYChEdiiEH5..."}'
```

<Warning>
Sign `messageToSign` exactly as returned. It is matched character for character, and the date it carries is only accepted for yesterday, today, and tomorrow in UTC. Never rebuild the message yourself.
</Warning>

**`LIVENESS`** returns a hosted `verificationLink` (and an embed `token` where supported). The owner completes a biometric flow and verification finishes asynchronously, so watch for `EXTERNAL_ACCOUNT.STATUS_UPDATED` or poll the account.

<Note>
Calling the challenge endpoint again while a signature challenge is outstanding returns that same challenge, so reloading a signing page does not invalidate the message the owner is about to sign. A refused signature moves the account to `UNVERIFIED`, where it stays until a retry succeeds.
</Note>

Verification applies only to first-party self-custody wallets. A wallet held at an exchange is declared by setting `vaspName` inside `accountInfo`, and skips verification because the keys belong to the exchange. `THIRD_PARTY` wallets skip it too.

<Warning>
`vaspName` belongs inside `accountInfo`. At the top level it is ignored, and the wallet is treated as self-custody.
</Warning>

### Using External Accounts

<Tabs>
Expand Down
24 changes: 18 additions & 6 deletions mintlify/ramps/accounts/external-accounts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,30 @@ curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/external-accou

External accounts move through verification states:

| Status | Description | Can Use for Conversions |
| ---------- | ------------------------ | ----------------------- |
| `PENDING` | Verification in progress | ❌ |
| `ACTIVE` | Verified and ready | ✅ |
| `UNDER_REVIEW` | Additional review required | ❌ |
| `INACTIVE` | Manually disabled | ❌ |
| Status | Description | Can use for conversions |
| -------------------------------- | ----------------------------------------------------------------- | ------------------------------ |
| `PENDING` | Verification in progress | No |
| `ACTIVE` | Verified and ready | Yes |
| `PENDING_OWNERSHIP_VERIFICATION` | The customer must prove they control this self-custody wallet | Below the regulatory threshold |
| `UNVERIFIED` | The last ownership attempt failed. Start a new challenge to retry | Below the regulatory threshold |
| `UNDER_REVIEW` | Additional review required | No |
| `INACTIVE` | Manually disabled | No |

<Info>
Spark wallet accounts are immediately `ACTIVE`. Bank accounts may require
verification (typically instant to a few hours).
</Info>

<Note>
A self-custody wallet registered with `ownershipType` set to `FIRST_PARTY`
starts at `PENDING_OWNERSHIP_VERIFICATION` in regions that require the owner to
prove they control it (e.g. the EU). It is not blocked: conversions below the
regulatory threshold go through while the wallet is pending. To clear the
status, start a challenge with
`POST /customers/external-accounts/{externalAccountId}/challenge` and complete
it with either a wallet signature or a liveness check.
</Note>

## Best practices for ramps

<AccordionGroup>
Expand Down
20 changes: 12 additions & 8 deletions mintlify/snippets/external-accounts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1970,14 +1970,18 @@ For business beneficiaries, the required fields vary by destination currency:
</Info>

## Account status
Beneficiary data may be reviewed for risk and compliance. Only `ACTIVE` accounts can receive payments. Updates to account data may trigger account re-review.

| Status | Description |
| -------------- | ----------------------------------- |
| `PENDING` | Created, awaiting verification |
| `ACTIVE` | Verified and ready for transactions |
| `UNDER_REVIEW` | Additional review required |
| `INACTIVE` | Disabled, cannot be used |
Beneficiary data may be reviewed for risk and compliance. Only `ACTIVE` accounts can receive payments, except that a self-custody wallet still owed ownership verification can receive transfers below the regulatory threshold. Updates to account data may trigger account re-review.

| Status | Description |
| -------------------------------- | --------------------------------------------------------------------------------- |
| `PENDING` | Created, awaiting verification |
| `ACTIVE` | Verified and ready for transactions |
| `PENDING_OWNERSHIP_VERIFICATION` | The customer must prove they control this self-custody wallet |
| `UNVERIFIED` | The last ownership attempt failed. Start a new challenge to retry |
| `UNDER_REVIEW` | Additional review required |
| `INACTIVE` | Disabled, cannot be used |

A wallet reaches `PENDING_OWNERSHIP_VERIFICATION` when you register it with `ownershipType` set to `FIRST_PARTY` in a region that requires the owner to prove they control it (e.g. the EU). To clear it, start a challenge with `POST /customers/external-accounts/{externalAccountId}/challenge` and complete it with either a wallet signature or a liveness check. Both statuses allow transfers below the regulatory threshold, so a pending wallet is not blocked outright.

## Listing external accounts

Expand Down
28 changes: 18 additions & 10 deletions openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ post:
asynchronously. The outcome is delivered via
`EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by polling the account.

Calling this endpoint again abandons any in-flight challenge and issues a
new one with the requested method — use it to retry after a failed
attempt, to replace an expired challenge, or to switch methods. An
`UNVERIFIED` account returns to `PENDING_OWNERSHIP_VERIFICATION` when a
new challenge is issued.
While a `WALLET_SIGNATURE` challenge is outstanding, calling this endpoint
again returns that same challenge, with the same `messageToSign` and
`expiresAt`. Reloading a signing page therefore does not invalidate the
message the user is about to sign. You get a new challenge once the
outstanding one expires or its attempt fails.

Requesting `LIVENESS` always starts a liveness attempt, even while a
signature challenge is outstanding.

An `UNVERIFIED` account stays `UNVERIFIED` until a retry succeeds.
Completing ownership verification moves the account to `ACTIVE`.
operationId: createExternalAccountOwnershipChallenge
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ post:
asynchronously. The outcome is delivered via
`EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by polling the account.

Calling this endpoint again abandons any in-flight challenge and issues a
new one with the requested method — use it to retry after a failed
attempt, to replace an expired challenge, or to switch methods. An
`UNVERIFIED` account returns to `PENDING_OWNERSHIP_VERIFICATION` when a
new challenge is issued.
While a `WALLET_SIGNATURE` challenge is outstanding, calling this endpoint
again returns that same challenge, with the same `messageToSign` and
`expiresAt`. Reloading a signing page therefore does not invalidate the
message the user is about to sign. You get a new challenge once the
outstanding one expires or its attempt fails.

Requesting `LIVENESS` always starts a liveness attempt, even while a
signature challenge is outstanding.

An `UNVERIFIED` account stays `UNVERIFIED` until a retry succeeds.
Completing ownership verification moves the account to `ACTIVE`.
operationId: createPlatformExternalAccountOwnershipChallenge
tags:
Expand Down
Loading