> ## Documentation Index
> Fetch the complete documentation index at: https://agents-docs.lightspark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementation overview

> End-to-end walkthrough: create a customer, find their Global Account, register a passkey, fund the account, and execute a signed withdrawal.

## Prerequisites

> [!WARNING]
>
> Customers who hold a Global Account must be KYC/KYB verified before any account funds can move from or to fiat rails. This quickstart picks up after KYC is complete.
>
> In sandbox, customers are automatically KYC approved on creation so you can skip straight to account setup.

You also need:

* A platform configured with `USDB` in its supported currencies. In sandbox, USDB is enabled by default alongside `USD` and `USDC`.
* Sandbox or production API credentials with access to the `Embedded Wallet Auth` and `Internal Accounts` endpoints.

```bash theme={null}
export GRID_BASE_URL="https://api.lightspark.com/grid/2025-10-13"
export GRID_CLIENT_ID="YOUR_SANDBOX_CLIENT_ID"
export GRID_CLIENT_SECRET="YOUR_SANDBOX_CLIENT_SECRET"
```

## Walkthrough

The walkthrough below is the happy path: create a customer, find the auto-provisioned account and its default email OTP credential, fund it, and withdraw to a bank account. Each step shows the HTTP request your integrator backend makes on behalf of the client.

### 1. Create a customer

Create the customer record. A Global Account is provisioned automatically whenever a customer is created on a platform that has `USDB` in its supported currencies — you don't need to pass it on the customer.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "INDIVIDUAL",
    "platformCustomerId": "ind-9f84e0c2",
    "region": "US",
    "email": "jane@example.com",
    "fullName": "Jane Doe",
    "birthDate": "1990-01-15",
    "nationality": "US"
  }'
```

**Response:** `201 Created` with the new `Customer:...` id. In sandbox, the customer is KYC-approved immediately; in production you would now run them through the KYC / KYB flow before any funds can move.

### 2. Find the Global Account

When a customer is created on a USDB-enabled platform, Grid automatically provisions a Global Account alongside their other internal accounts. Fetch it by filtering the customer's internal accounts by `type=EMBEDDED_WALLET`.

```bash theme={null}
curl -X GET "$GRID_BASE_URL/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001&type=EMBEDDED_WALLET" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "type": "EMBEDDED_WALLET",
      "balance": {
        "amount": 0,
        "currency": {
          "code": "USDB",
          "name": "USDB",
          "decimals": 6
        }
      },
      "totalBalance": {
        "amount": 0,
        "currency": {
          "code": "USDB",
          "name": "USDB",
          "decimals": 6
        }
      },
      "fundingPaymentInstructions": [],
      "createdAt": "2026-04-19T12:00:00Z",
      "updatedAt": "2026-04-19T12:00:00Z"
    }
  ],
  "hasMore": false,
  "totalCount": 1
}
```

Hold onto the `InternalAccount:...` id — every auth credential is scoped to it.

### 3. Find the default email OTP credential

Global Accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on file. Fetch the auth methods for the account and keep the `AuthMethod:...` id for the signing step later in this walkthrough.

```bash theme={null}
curl -X GET "$GRID_BASE_URL/auth/credentials?accountId=InternalAccount:019542f5-b3e7-1d02-0000-000000000002" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "AuthMethod:019542f5-b3e7-1d02-0000-000000000001",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "type": "EMAIL_OTP",
      "nickname": "jane@example.com",
      "createdAt": "2026-04-19T12:00:01Z",
      "updatedAt": "2026-04-19T12:00:01Z"
    }
  ]
}
```

You can add passkeys or OAuth credentials later, but adding credentials is itself a signed action. Start with the default email OTP credential to mint the first session signing key.

### 4. Fund the Account

Global Accounts behave like any other internal account on the way in — incoming funds do not need the customer's signature. In sandbox, use the sandbox funding endpoint to skip straight to a funded state:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/sandbox/internal-accounts/InternalAccount:019542f5-b3e7-1d02-0000-000000000002/fund" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000000000
  }'
```

`amount` is in the smallest unit of the account's currency. USDB has 6 decimals, so `1000000000` is 1,000.00 USDB.

You will receive an `INCOMING_PAYMENT` webhook when the balance updates. The account now holds 1,000.00 USDB.

> [!TIP]
>
> To fund from another currency (USD ACH, USDC on-chain, etc.), create a quote with `destination.destinationType: "ACCOUNT"` pointing at the Global Account's `InternalAccount` id. The quote's `sourceCurrency` can be any supported platform currency; Grid will convert into USDB on execute.

### 5. Add an external bank account

Add the destination the customer wants to withdraw to. This is a standard external account — nothing Global Account-specific.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/customers/external-accounts" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "USD",
    "platformAccountId": "jane_doe_checking",
    "accountInfo": {
      "accountType": "USD_ACCOUNT",
      "accountNumber": "1234567890",
      "routingNumber": "021000021",
      "bankAccountType": "CHECKING",
      "beneficiary": {
        "beneficiaryType": "INDIVIDUAL",
        "fullName": "Jane Doe",
        "birthDate": "1990-01-15",
        "nationality": "US",
        "address": {
          "line1": "123 Main Street",
          "city": "San Francisco",
          "state": "CA",
          "postalCode": "94105",
          "country": "US"
        }
      }
    }
  }'
```

**Response:** `201 Created` with the new `ExternalAccount:...` id.

### 6. Create a withdrawal quote

Create a quote with the Global Account as the source. Grid returns a `payloadToSign` in the quote's payment instructions — this is what the client will sign to authorize the transfer.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/quotes" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "sourceType": "ACCOUNT",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
    },
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 10000000,
    "description": "Withdrawal to checking"
  }'
```

`lockedCurrencyAmount` is in the smallest unit of the locked side's currency. Here the sending currency is USDB (6 decimals), so `10000000` is 10.00 USDB.

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PENDING",
  "createdAt": "2026-04-19T12:05:00Z",
  "expiresAt": "2026-04-19T12:10:00Z",
  "source": {
    "sourceType": "ACCOUNT",
    "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
  },
  "destination": {
    "destinationType": "ACCOUNT",
    "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
  },
  "sendingCurrency": { "code": "USDB", "name": "USDB", "decimals": 6 },
  "receivingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 },
  "totalSendingAmount": 10000000,
  "totalReceivingAmount": 975,
  "exchangeRate": 1.0,
  "feesIncluded": 250000,
  "platformFeesIncluded": 0,
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000005",
  "paymentInstructions": [
    {
      "accountOrWalletInfo": {
        "accountType": "EMBEDDED_WALLET",
        "payloadToSign": "{\"type\":\"ACTIVITY_TYPE_SIGN_TRANSACTION_V2\",\"timestampMs\":\"1746736509954\",\"organizationId\":\"org_abc123\",\"parameters\":{\"signWith\":\"wallet_abc123def456\",\"unsignedTransaction\":\"ea69b4bf05f775209f26ff0a34a05569180f7936579d5c4af9377ae550194f72\",\"type\":\"TRANSACTION_TYPE_ETHEREUM\"},\"generateAppProofs\":true}"
      },
        "instructionsNotes": "Stamp the payloadToSign byte-for-byte and pass the stamp as the Grid-Wallet-Signature header on execute"
    }
  ]
}
```

### 7. Authenticate and sign

The customer has an outstanding quote with a `payloadToSign`. Now we need a session signing key to sign it with. With `EMAIL_OTP`, the client generates a TEK (Target Encryption Key) pair, HPKE-encrypts the OTP code, and uses the TEK private key both to complete login and to sign the quote payload.

**1. Your backend requests a fresh OTP**

Ask Grid to send a fresh OTP email for the default `EMAIL_OTP` credential. The response includes `otpEncryptionTargetBundle` for the secure OTP flow.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/challenge" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

**Response (200):**

```json theme={null}
{
  "id": "AuthMethod:019542f5-b3e7-1d02-0000-000000000001",
  "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
  "type": "EMAIL_OTP",
  "nickname": "jane@example.com",
  "otpEncryptionTargetBundle": "{\"version\":\"v1.0.0\",\"data\":\"7b22...\",\"dataSignature\":\"3045...\",\"enclaveQuorumPublic\":\"04a1...\"}",
  "createdAt": "2026-04-19T12:00:01Z",
  "updatedAt": "2026-04-19T12:05:00Z"
}
```

Return `otpEncryptionTargetBundle` to the client.

**2. Client encrypts the OTP and verifies**

The client generates a fresh [P-256 key pair (the TEK)](https://agents-docs.lightspark.com/global-accounts/client-keys.md#1-generate-a-client-key-pair), [HPKE-encrypts](https://agents-docs.lightspark.com/global-accounts/client-keys.md#encrypt-the-otp-code-email_otp-only) `{otp_code, public_key}` under `otpEncryptionTargetBundle`, and sends the encrypted bundle to your backend. `public_key` must be the compressed public key (`02` or `03` prefix). In sandbox, use OTP code `000000`.

Your backend calls verify with the encrypted bundle:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/verify" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "EMAIL_OTP",
    "encryptedOtpBundle": "{\"encappedPublic\":\"044f631a...\",\"ciphertext\":\"1fa1023390...\"}"
  }'
```

**Response (202):**

```json theme={null}
{
  "type": "EMAIL_OTP",
  "payloadToSign": "{\"login\":{\"publicKey\":\"02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31\"},\"tokenId\":\"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21\",\"type\":\"USAGE_TYPE_LOGIN\"}",
  "requestId": "Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21",
  "expiresAt": "2026-04-19T12:10:00Z"
}
```

Return `payloadToSign` and `requestId` to the client.

**3. Client stamps payloadToSign and completes login**

The client [stamps](https://agents-docs.lightspark.com/global-accounts/client-keys.md#2-sign-a-payloadtosign) the exact UTF-8 bytes of `payloadToSign`, unchanged, with the TEK private key and sends the stamp back to your backend.

Your backend retries the same request with the stamp:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/verify" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "Grid-Wallet-Signature: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9" \
  -H "Request-Id: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
  -d '{
    "type": "EMAIL_OTP",
    "encryptedOtpBundle": "{\"encappedPublic\":\"044f631a...\",\"ciphertext\":\"1fa1023390...\"}"
  }'
```

**Response (200):**

```json theme={null}
{
  "id": "Session:019542f5-b3e7-1d02-0000-000000000003",
  "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
  "type": "EMAIL_OTP",
  "nickname": "jane@example.com",
  "createdAt": "2026-04-19T12:05:01Z",
  "updatedAt": "2026-04-19T12:05:01Z",
  "expiresAt": "2026-04-19T12:20:01Z"
}
```

The TEK public key is now the session API key. The TEK private key **is** the session signing key — the client already has it.

**4. Client stamps the quote payload**

The client [signs the quote's `payloadToSign`](client-keys#2-sign-a-payloadtosign) with the same TEK private key. Return the full Grid wallet signature to your backend.

> [!WARNING]
>
> Stamp the `payloadToSign` bytes exactly as Grid returned them. Do not parse, re-serialize, trim, or normalize the JSON — the stamp must cover the same bytes Grid's verifier hashes.

The session signing key is now valid for 15 minutes, so subsequent account actions within that window (for example, a second withdrawal) can reuse it without another `/challenge` + `/verify` round-trip.

### 8. Execute the quote

Call `/execute` with the stamp in the `Grid-Wallet-Signature` header.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/quotes/Quote:019542f5-b3e7-1d02-0000-000000000006/execute" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
  -H "Grid-Wallet-Signature: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9"
```

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PROCESSING",
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000005",
  "totalSendingAmount": 10000000,
  "totalReceivingAmount": 975,
  "feesIncluded": 250000
}
```

The transaction is on its way. You'll receive standard transaction webhooks (`OUTGOING_PAYMENT`) as it settles — see [Transaction lifecycle](https://agents-docs.lightspark.com/platform-overview/core-concepts/transaction-lifecycle.md).

## Where to next

> [card] [Client keys & signing](https://agents-docs.lightspark.com/global-accounts/client-keys.md)
>
> Generate the P-256 key pair, hold the session signing key, and sign payloads on Web, iOS, and Android.

> [card] [Authentication](https://agents-docs.lightspark.com/global-accounts/authentication.md)
>
> OAuth and Email OTP flows, passkey reauthentication, and the full WebAuthn parameter mapping.

> [card] [Sessions](https://agents-docs.lightspark.com/global-accounts/managing-sessions.md)
>
> List, refresh, and revoke active sessions.

> [card] [Exporting a wallet](https://agents-docs.lightspark.com/global-accounts/exporting-wallet.md)
>
> Let a customer take their wallet seed off Grid.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.