> ## Documentation Index
> Fetch the complete documentation index at: https://ifemafia.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /api/wallets — provision a new agent wallet

> Create a scoped wallet for an AI agent. Sets per-transaction and period spend limits, expiry, and optional merchant allowlist. Returns an API key and virtual account.

Provision a new wallet for an AI agent. Each wallet is fully isolated — it carries its own
virtual bank account, spend controls, and API key. The agent uses that key to authorise
payments; your backend never touches the agent's funds directly.

> **Canonical path:** `POST /api/wallets`
> `POST /api/onboarding` redirects to this endpoint — use the canonical path for all new integrations.

## Endpoint

```
POST /api/wallets
```

## Authentication

Requires a valid **Session JWT** in the `Authorization` header.

```
Authorization: Bearer <session_jwt>
```

***

## Request Body

<ParamField body="name" type="string" required>
  Human-readable label for this wallet. Used in the dashboard and transaction receipts.
  Must be between **1 and 100 characters**.
</ParamField>

<ParamField body="userId" type="string" required>
  The ID of the user who owns this wallet, as returned during sign-up. Must be a valid UUID.
</ParamField>

<ParamField body="spendLimitPerTx" type="integer" required>
  Maximum amount the agent may spend in a **single transaction**, denominated in **kobo**
  (100 kobo = ₦1). Requests above this threshold are automatically blocked.
  Must be a positive integer and **must not exceed `spendLimitPeriod`**.
</ParamField>

<ParamField body="spendLimitPeriod" type="integer" required>
  Maximum **cumulative** spend allowed within the rolling window defined by `periodWindowDays`,
  denominated in **kobo**. Must be greater than or equal to `spendLimitPerTx`.
</ParamField>

<ParamField body="periodWindowDays" type="integer" required default="30">
  Length of the rolling spend window, in **days**. The period counter resets on a rolling basis
  — not calendar-month aligned. Defaults to `30`.
</ParamField>

<ParamField body="expiry" type="string" required>
  ISO 8601 datetime at which this wallet should automatically deactivate (e.g.
  `"2026-01-31T23:59:59Z"`). Must be a timestamp in the future at the time of the request.
</ParamField>

<ParamField body="useAllowlist" type="boolean" default="false">
  When `true`, the wallet will only approve payments to merchants listed in `merchantAllowlist`.
  All other merchants are blocked regardless of spend limits.
</ParamField>

<ParamField body="merchantAllowlist" type="string[]">
  An array of merchant name strings. **Required when `useAllowlist` is `true`** (must contain
  at least one entry). Merchant names are matched case-insensitively against the name reported
  by the payment terminal at authorisation time.

  ```json theme={null}
  ["Shopify Store", "AWS", "Vercel"]
  ```
</ParamField>

***

## Cross-field Validation Rules

The following constraints are enforced in addition to individual field validation:

* **`spendLimitPerTx` ≤ `spendLimitPeriod`** — the per-transaction cap cannot exceed the
  rolling-period cap. A `400` error is returned if this rule is violated.
* **`merchantAllowlist` required when `useAllowlist` is `true`** — the array must contain at
  least one merchant name. Omitting it or sending an empty array returns `400`.
* **`expiry` must be in the future** — the timestamp is validated against the server clock at
  request time. A past expiry returns `400`.

***

## Example Request

```json theme={null}
{
  "name": "Procurement Agent — Q3 2025",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "spendLimitPerTx": 500000,
  "spendLimitPeriod": 2000000,
  "periodWindowDays": 30,
  "expiry": "2027-01-01T00:00:00.000Z",
  "useAllowlist": true,
  "merchantAllowlist": ["AWS", "Vercel", "Stripe"]
}
```

***

## Response `201 Created`

The response envelope contains `success: true` and a `data` object with three nested objects:
`wallet`, `virtualAccount`, and `apiKey`.

<ResponseField name="success" type="boolean">
  Always `true` for a successful creation.
</ResponseField>

<ResponseField name="data" type="object">
  Container for the created resources.

  <Expandable title="data fields">
    <ResponseField name="data.wallet" type="object">
      The newly created wallet object.

      <Expandable title="wallet fields">
        <ResponseField name="data.wallet.id" type="string">
          Unique wallet UUID. Use this as `walletId` in all subsequent requests.
        </ResponseField>

        <ResponseField name="data.wallet.name" type="string">
          The label supplied at creation time.
        </ResponseField>

        <ResponseField name="data.wallet.userId" type="string">
          The owning user's ID.
        </ResponseField>

        <ResponseField name="data.wallet.businessId" type="string | null">
          The associated business ID, or `null` if not linked to a business account.
        </ResponseField>

        <ResponseField name="data.wallet.balance" type="integer">
          Current wallet balance in kobo. Always `0` for a freshly created wallet.
        </ResponseField>

        <ResponseField name="data.wallet.spendLimitPerTx" type="integer">
          Per-transaction spend cap, in kobo.
        </ResponseField>

        <ResponseField name="data.wallet.spendLimitPeriod" type="integer">
          Rolling-period spend cap, in kobo.
        </ResponseField>

        <ResponseField name="data.wallet.periodWindowDays" type="integer">
          Length of the rolling window in days.
        </ResponseField>

        <ResponseField name="data.wallet.useAllowlist" type="boolean">
          Whether merchant-allowlist enforcement is active.
        </ResponseField>

        <ResponseField name="data.wallet.merchantAllowlist" type="string[]">
          List of permitted merchant names (empty array when `useAllowlist` is `false`).
        </ResponseField>

        <ResponseField name="data.wallet.expiry" type="string">
          ISO 8601 expiry timestamp as supplied in the request.
        </ResponseField>

        <ResponseField name="data.wallet.expiresAt" type="string | null">
          Resolved expiry datetime used internally. May be `null` until processed.
        </ResponseField>

        <ResponseField name="data.wallet.status" type="string">
          Wallet lifecycle status. `"ACTIVE"` immediately after creation.
        </ResponseField>

        <ResponseField name="data.wallet.apiKeyHash" type="string | null">
          Hashed representation of the API key stored server-side. `null` at creation time —
          this field is not useful to API consumers and is included for completeness only.
        </ResponseField>

        <ResponseField name="data.wallet.createdAt" type="string">
          ISO 8601 timestamp of wallet creation.
        </ResponseField>

        <ResponseField name="data.wallet.updatedAt" type="string">
          ISO 8601 timestamp of the most recent update.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.virtualAccount" type="object">
      The virtual bank account automatically provisioned for this wallet.

      <Expandable title="virtualAccount fields">
        <ResponseField name="data.virtualAccount.id" type="string">
          Virtual account UUID.
        </ResponseField>

        <ResponseField name="data.virtualAccount.walletId" type="string">
          The wallet this virtual account is linked to.
        </ResponseField>

        <ResponseField name="data.virtualAccount.accountNumber" type="string">
          10-digit Nigerian bank account number assigned to this wallet.
        </ResponseField>

        <ResponseField name="data.virtualAccount.accountName" type="string">
          Account name as it appears on bank transfers.
        </ResponseField>

        <ResponseField name="data.virtualAccount.bankName" type="string">
          The issuing bank name (e.g. `"Providus Bank"`).
        </ResponseField>

        <ResponseField name="data.virtualAccount.providerRef" type="string">
          Provider-assigned reference for this virtual account.
        </ResponseField>

        <ResponseField name="data.virtualAccount.createdAt" type="string">
          ISO 8601 timestamp of virtual account creation.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.apiKey" type="object">
      Credentials the agent uses to authorise payments.

      <Expandable title="apiKey fields">
        <ResponseField name="data.apiKey.id" type="string">
          API key UUID.
        </ResponseField>

        <ResponseField name="data.apiKey.keyPrefix" type="string">
          The first several characters of the key — safe to store and display for identification.
        </ResponseField>

        <ResponseField name="data.apiKey.fullKey" type="string">
          The **complete** API key string. See warning below.
        </ResponseField>

        <ResponseField name="data.apiKey.createdAt" type="string">
          ISO 8601 timestamp of key creation.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

### Example Response

```json theme={null}
{
  "success": true,
  "data": {
    "wallet": {
      "id": "wallet-uuid",
      "name": "Purchasing Agent v1",
      "userId": "user-uuid",
      "businessId": null,
      "balance": 0,
      "spendLimitPerTx": 500000,
      "spendLimitPeriod": 2000000,
      "periodWindowDays": 30,
      "merchantAllowlist": [],
      "useAllowlist": false,
      "expiresAt": null,
      "expiry": "2027-01-01T00:00:00.000Z",
      "status": "ACTIVE",
      "apiKeyHash": null,
      "createdAt": "2026-07-21T09:00:00.000Z",
      "updatedAt": "2026-07-21T09:00:00.000Z"
    },
    "virtualAccount": {
      "id": "va-uuid",
      "walletId": "wallet-uuid",
      "accountNumber": "9876543210",
      "accountName": "Purchasing Agent v1",
      "bankName": "Providus Bank",
      "providerRef": "mock-ref-abc123",
      "createdAt": "2026-07-21T09:00:00.000Z"
    },
    "apiKey": {
      "id": "key-uuid",
      "keyPrefix": "ax_live_9f2a3b",
      "fullKey": "ax_live_9f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2",
      "createdAt": "2026-07-21T09:00:00.000Z"
    }
  }
}
```

<Warning>
  **`data.apiKey.fullKey` is returned exactly once** — at creation time. It is never returned
  again in any subsequent API response. Store it securely (e.g. in a secrets manager or
  encrypted environment variable) before discarding the response. If the key is lost, you must
  rotate it via the key-management endpoint.
</Warning>

***

## Error Responses

| Status | Code                   | Description                                                                                                              |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `VALIDATION_ERROR`     | One or more request body fields failed validation. Check the `errors` array in the response body for field-level detail. |
| `400`  | `EXPIRY_IN_PAST`       | The `expiry` timestamp is not in the future.                                                                             |
| `400`  | `ALLOWLIST_REQUIRED`   | `useAllowlist` is `true` but `merchantAllowlist` is missing or empty.                                                    |
| `400`  | `PERIOD_LIMIT_TOO_LOW` | `spendLimitPeriod` is less than `spendLimitPerTx`.                                                                       |
| `401`  | `UNAUTHORIZED`         | Missing or invalid Session JWT.                                                                                          |

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request body validation failed.",
    "errors": [
      {
        "field": "spendLimitPeriod",
        "message": "spendLimitPeriod must be >= spendLimitPerTx"
      }
    ]
  }
}
```
