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

# Axis API Base URL, Request Format, and Error Handling

> Every Axis API request targets a versioned base URL. Learn the two route namespaces, standard error shape, and how to handle common HTTP status codes.

Axis exposes two route namespaces that reflect the natural split between **user-facing operations** (auth and onboarding) and **agent-facing operations** (wallets, payments, and ledger). Both namespaces live under the same base URL and follow the same JSON conventions — understanding which namespace to target and what to expect when something goes wrong will save you debugging time early.

## Route namespaces

### `/v1/` — Auth, onboarding, and wallet data

The `/v1/` namespace handles identity: signing users up, issuing session tokens, and bootstrapping their first wallet through the onboarding flow. It also exposes read endpoints for wallet stats and transaction history scoped to the authenticated session.

| Method | Path                           | Description                              |
| ------ | ------------------------------ | ---------------------------------------- |
| `POST` | `/v1/auth/signup`              | Create a new user account                |
| `POST` | `/v1/auth/login`               | Exchange credentials for a session token |
| `POST` | `/v1/auth/logout`              | Invalidate the current session token     |
| `POST` | `/v1/onboarding`               | Run the post-signup onboarding flow      |
| `GET`  | `/v1/wallets/:id/stats`        | Fetch aggregate stats for a wallet       |
| `GET`  | `/v1/wallets/:id/transactions` | List transactions scoped to a wallet     |

### `/api/` — Wallets and payments

The `/api/` namespace is where wallets are provisioned and agents submit payment intents. Wallet CRUD routes require a session token; the payment intent route requires a wallet API key.

| Method | Path                        | Description                               |
| ------ | --------------------------- | ----------------------------------------- |
| `POST` | `/api/onboarding`           | Alternative onboarding route (also valid) |
| `POST` | `/api/wallets`              | Provision a new wallet                    |
| `GET`  | `/api/wallets/:walletId`    | Fetch a wallet by ID                      |
| `GET`  | `/api/wallets/user/:userId` | List all wallets for a user               |
| `POST` | `/api/payment-intent`       | Create and authorize a payment intent     |

***

## Request format

Every request to the Axis API must include a `Content-Type: application/json` header and a JSON body (for `POST` requests). Authenticated routes require one of two credentials:

* **`Authorization: Bearer <token>`** — a session token returned by `POST /v1/auth/login`, used with `/v1/` routes and wallet management routes.
* **`x-api-key: <api-key>`** — a wallet-scoped API key (`ax_live_` + 48 hex chars), used with `POST /api/payment-intent`.

```typescript theme={null}
const BASE_URL = "https://api.axispayments.ai";

async function axisRequest<T>(
  path: string,
  options: {
    method?: "GET" | "POST" | "PATCH" | "DELETE";
    body?: unknown;
    apiKey?: string;
    token?: string;
  } = {}
): Promise<T> {
  const { method = "GET", body, apiKey, token } = options;

  const headers: Record<string, string> = {
    "Content-Type": "application/json",
  };

  if (apiKey) {
    headers["x-api-key"] = apiKey;
  } else if (token) {
    headers["Authorization"] = `Bearer ${token}`;
  }

  const response = await fetch(`${BASE_URL}${path}`, {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });

  if (!response.ok) {
    const error = await response.json();
    throw new AxisApiError(response.status, error);
  }

  return response.json() as Promise<T>;
}
```

***

## Error response shape

When a request fails, Axis returns a JSON body with a consistent structure. `status` signals the failure, `message` gives a human-readable description, and the optional `details` object provides field-level validation errors — useful for surfacing inline form errors directly in your UI.

```json theme={null}
{
  "status": "fail",
  "message": "invalid request body",
  "details": {
    "amount": ["Must be greater than 0"]
  }
}
```

| Field     | Type     | Always present | Description                                        |
| --------- | -------- | -------------- | -------------------------------------------------- |
| `status`  | `string` | ✓              | `"fail"` or `"error"` on failure responses         |
| `message` | `string` | ✓              | Human-readable description of the failure          |
| `details` | `object` | ✗              | Field-keyed validation errors (400 responses only) |

<Note>
  Some error responses use `success: false` instead of `status: "fail"`. Your error handler should treat both shapes as failure cases.
</Note>

***

## HTTP status codes

| Status | Meaning               | When you'll see it                                                    |
| ------ | --------------------- | --------------------------------------------------------------------- |
| `200`  | OK                    | Successful `GET` requests                                             |
| `201`  | Created               | `POST` requests that create a new resource                            |
| `400`  | Bad Request           | Missing or invalid fields in the request body                         |
| `401`  | Unauthorized          | API key or session token is missing or invalid                        |
| `402`  | Insufficient Funds    | The wallet does not have enough balance to cover the payment          |
| `403`  | Forbidden             | The authorization engine rejected the payment (see error codes below) |
| `404`  | Not Found             | The requested resource does not exist                                 |
| `500`  | Internal Server Error | Something went wrong on the Axis backend                              |

***

## Authorization error codes

When a payment intent is rejected with a `403`, the response body includes a machine-readable `code` field from the authorization engine. Use this to give users and agents specific, actionable feedback rather than a generic error message.

| Code                              | Meaning                                                          |
| --------------------------------- | ---------------------------------------------------------------- |
| `MISSING_API_KEY`                 | No `x-api-key` header was included in the request                |
| `INVALID_API_KEY`                 | The provided API key does not match any active key               |
| `WALLET_INACTIVE`                 | The wallet exists but has been deactivated                       |
| `WALLET_EXPIRED`                  | The wallet's validity period has passed                          |
| `SPEND_LIMIT_PER_TX_EXCEEDED`     | The payment amount exceeds the per-transaction spend limit       |
| `SPEND_LIMIT_PER_PERIOD_EXCEEDED` | The payment would exceed the wallet's rolling period spend limit |
| `MERCHANT_NOT_ALLOWED`            | The destination merchant is not on the wallet's allowlist        |
| `INSUFFICIENT_FUNDS`              | The wallet balance is too low to cover the payment amount        |

***

## Handling errors in your frontend

The snippet below shows a complete error-handling wrapper built around the request helper defined earlier. `AxisApiError` preserves both the HTTP status and the parsed response body so you can branch on `code` for authorization failures and on `details` for validation failures.

```typescript theme={null}
// axis-error.ts

export interface AxisErrorBody {
  status: string;
  message: string;
  code?: AxisErrorCode;
  details?: Record<string, string[]>;
}

export type AxisErrorCode =
  | "MISSING_API_KEY"
  | "INVALID_API_KEY"
  | "WALLET_INACTIVE"
  | "WALLET_EXPIRED"
  | "SPEND_LIMIT_PER_TX_EXCEEDED"
  | "SPEND_LIMIT_PER_PERIOD_EXCEEDED"
  | "MERCHANT_NOT_ALLOWED"
  | "INSUFFICIENT_FUNDS";

export class AxisApiError extends Error {
  status: number;
  body: AxisErrorBody;

  constructor(status: number, body: AxisErrorBody) {
    super(body.message);
    this.name = "AxisApiError";
    this.status = status;
    this.body = body;
  }

  /** True when the authorization engine rejected the payment. */
  isAuthorizationFailure(): boolean {
    return this.status === 403;
  }

  /** True when request body validation failed — check `body.details` for field errors. */
  isValidationError(): boolean {
    return this.status === 400;
  }
}

// Usage example in a React component:
async function submitPayment(walletId: string, amount: number) {
  try {
    const result = await axisRequest("/api/payment-intent", {
      method: "POST",
      apiKey: process.env.NEXT_PUBLIC_AXIS_API_KEY,
      body: { walletId, amount },
    });
    return result;
  } catch (err) {
    if (err instanceof AxisApiError) {
      if (err.isAuthorizationFailure()) {
        switch (err.body.code) {
          case "INSUFFICIENT_FUNDS":
            showToast("Your wallet doesn't have enough funds for this payment.");
            break;
          case "SPEND_LIMIT_PER_TX_EXCEEDED":
            showToast("This payment exceeds the per-transaction spend limit.");
            break;
          case "MERCHANT_NOT_ALLOWED":
            showToast("This merchant is not permitted for your wallet.");
            break;
          case "WALLET_INACTIVE":
          case "WALLET_EXPIRED":
            showToast("Your wallet is no longer active. Please contact support.");
            break;
          default:
            showToast(`Payment declined: ${err.message}`);
        }
      } else if (err.isValidationError() && err.body.details) {
        // Surface field-level errors in a form
        setFieldErrors(err.body.details);
      } else if (err.status === 401) {
        redirectToLogin();
      } else {
        showToast("Something went wrong. Please try again.");
      }
    }
    throw err;
  }
}
```
