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

# Overview

> Charge cards server-to-server, confirm 3DS challenges, retrieve payment details, list transactions, and issue refunds using the Axra Pay Payments API.

The Payments API gives you full programmatic control over the payment lifecycle. You can charge cards directly from your server without a hosted page, retrieve individual payments or paginated transaction lists, and issue refunds — all authenticated with your API key. Every charge is automatically deduplicated within a one-minute window, and 3D Secure challenges are surfaced as a structured `requires_action` response so you can redirect the cardholder without changing your integration.

<Note>
  The base URL for all Axra Pay API requests is `https://api.useaxra.com/api/v1`. All requests must be made over HTTPS.
</Note>

<CardGroup cols={3}>
  <Card title="Checkout sessions" icon="store" href="/payments/checkout-sessions">
    Hosted checkout for cards and local rails.
  </Card>

  <Card title="Card payments" icon="credit-card" href="/payments/card-payments">
    Server-to-server card charging.
  </Card>

  <Card title="Local payment methods" icon="landmark" href="/payments/local-payment-methods">
    Bank transfers, mobile money, and instant EFT settled in USDC.
  </Card>
</CardGroup>

***

## Charge a card (server-to-server)

`POST /business/payment/charge`

Use this endpoint to charge a card directly from your server. You must have server-to-server (S2S) charging enabled on your business account. If the card requires a 3DS challenge, the response will contain a `requiresAction` object instead of an immediate `succeeded` status — see [3DS authentication](/payments/3ds-authentication) for the full flow.

<Warning>
  You must enable server-to-server charging in your Axra Pay dashboard before using this endpoint. Calls from accounts without S2S enabled will return a `400` error.
</Warning>

### Authentication

Pass your API key in the `x-api-key` header, or include a valid JWT `Authorization: Bearer <token>` header on every request.

### Request parameters

**Card details** — provide either `card` or `savedTokenId`, not both.

<ParamField body="amount" type="number" required>
  Charge amount in major currency units (e.g., `49.99` for \$49.99). Do not pass cents.
</ParamField>

<ParamField body="currency" type="string" required>
  ISO 4217 currency code in lowercase (e.g., `"usd"`, `"eur"`, `"ngn"`).
</ParamField>

<ParamField body="card" type="object">
  Raw card details. Required if `savedTokenId` is not provided. Supply this object only over a server-to-server connection — never expose card data on the client.

  <Expandable title="card properties">
    <ParamField body="card.number" type="string">
      Full card number, digits only, no spaces.
    </ParamField>

    <ParamField body="card.expiryMonth" type="number">
      Expiry month as an integer from `1` to `12`.
    </ParamField>

    <ParamField body="card.expiryYear" type="number">
      Expiry year as a 4-digit integer (e.g., `2027`).
    </ParamField>

    <ParamField body="card.cvv" type="string">
      Card verification value (3 or 4 digits).
    </ParamField>

    <ParamField body="card.holderName" type="string">
      Cardholder name as it appears on the card. Optional but recommended.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="savedTokenId" type="string">
  A previously saved card token. Required if `card` is not provided. Tokens are created when `saveCard: true` is included in a prior charge request.
</ParamField>

<ParamField body="email" type="string">
  Customer email address. Used to send payment receipts.
</ParamField>

<ParamField body="description" type="string">
  A short description of the payment (e.g., `"Order #1042"`). Stored on the payment record and visible in your dashboard.
</ParamField>

<ParamField body="customerIp" type="string">
  Customer's IP address. Recommended — Axra Pay's fraud scoring engine uses this signal to assess risk.
</ParamField>

<ParamField body="returnUrl" type="string">
  URL to redirect the customer to after they complete (or cancel) a 3DS challenge. Required if your cards may trigger 3DS.
</ParamField>

<ParamField body="billingAddress" type="object">
  Customer billing address. Improves authorization rates and is required by some card networks for address verification.

  <Expandable title="billingAddress properties">
    <ParamField body="billingAddress.firstName" type="string">First name.</ParamField>
    <ParamField body="billingAddress.lastName" type="string">Last name.</ParamField>
    <ParamField body="billingAddress.address" type="string">Street address.</ParamField>
    <ParamField body="billingAddress.city" type="string">City.</ParamField>
    <ParamField body="billingAddress.state" type="string">State or province.</ParamField>
    <ParamField body="billingAddress.zip" type="string">Postal or ZIP code.</ParamField>
    <ParamField body="billingAddress.country" type="string">ISO 3166-1 alpha-2 country code (e.g., `"NG"`, `"GH"`).</ParamField>
  </Expandable>
</ParamField>

<ParamField body="saveCard" type="boolean" default="false">
  When `true`, Axra Pay tokenizes the card and returns a `savedTokenId` you can use for future charges without requiring the customer to re-enter card details.
</ParamField>

<ParamField body="metadata" type="object">
  Arbitrary key-value pairs (string keys, string or number values). Stored on the payment and returned in webhook events. Useful for attaching internal order IDs or customer references.
</ParamField>

### Responses

**Immediate success — `status: "succeeded"`**

<ResponseField name="paymentId" type="string" required>
  Unique Axra Pay identifier for this payment.
</ResponseField>

<ResponseField name="status" type="string" required>
  `"succeeded"` — the charge was accepted by the card network.
</ResponseField>

<ResponseField name="amount" type="number" required>
  Charged amount in major currency units, matching the request.
</ResponseField>

<ResponseField name="currency" type="string" required>
  ISO 4217 currency code.
</ResponseField>

<ResponseField name="transactionId" type="string" required>
  Axra transaction reference for this charge. Required when calling `POST /business/payment/confirm-3ds`.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  ISO 8601 timestamp of when the charge was created.
</ResponseField>

<Warning>
  A `status: "succeeded"` response means the charge was **accepted**, but the payment record remains in `PENDING` state until the `payment.completed` webhook fires. Do not fulfil orders based on the charge response alone — always wait for the webhook.
</Warning>

**3DS required — `status: "requires_action"`**

<ResponseField name="status" type="string" required>
  `"requires_action"` — the card issuer requires an additional authentication step before completing the charge.
</ResponseField>

<ResponseField name="requiresAction" type="object" required>
  <Expandable title="requiresAction properties">
    <ResponseField name="type" type="string">
      Authentication type. One of `"3ds_redirect"`, `"3ds_challenge"`, or `"fingerprint"`.
    </ResponseField>

    <ResponseField name="redirectUrl" type="string">
      URL to redirect the customer's browser to in order to complete the 3DS challenge. After the challenge, the customer is sent to your `returnUrl`.
    </ResponseField>
  </Expandable>
</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.useaxra.com/api/v1/business/payment/charge \
    --header 'x-api-key: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
      "amount": 49.99,
      "currency": "usd",
      "card": {
        "number": "4242424242424242",
        "expiryMonth": 12,
        "expiryYear": 2027,
        "cvv": "123",
        "holderName": "Amara Osei"
      },
      "email": "amara@example.com",
      "description": "Order #1042",
      "customerIp": "197.210.55.10",
      "returnUrl": "https://yourapp.com/payment/callback",
      "saveCard": false,
      "metadata": {
        "orderId": "1042",
        "customerId": "cust_abc123"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://api.useaxra.com/api/v1/business/payment/charge',
    {
      method: 'POST',
      headers: {
        'x-api-key': process.env.AXRA_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        amount: 49.99,
        currency: 'usd',
        card: {
          number: '4242424242424242',
          expiryMonth: 12,
          expiryYear: 2027,
          cvv: '123',
          holderName: 'Amara Osei',
        },
        email: 'amara@example.com',
        description: 'Order #1042',
        customerIp: '197.210.55.10',
        returnUrl: 'https://yourapp.com/payment/callback',
        saveCard: false,
        metadata: { orderId: '1042', customerId: 'cust_abc123' },
      }),
    }
  );

  const payment = await response.json();

  if (payment.status === 'requires_action') {
    // Redirect the customer to complete 3DS
    redirect(payment.requiresAction.redirectUrl);
  } else if (payment.status === 'succeeded') {
    // Wait for payment.completed webhook before fulfilling
    console.log('Charge accepted:', payment.paymentId);
  }
  ```
</CodeGroup>

**Success response**

```json theme={null}
{
  "paymentId": "pay_01HXYZ123456",
  "status": "succeeded",
  "amount": 49.99,
  "currency": "usd",
  "transactionId": "txn_9a3f21bc",
  "createdAt": "2026-04-14T10:23:45.000Z"
}
```

**3DS required response**

```json theme={null}
{
  "paymentId": "pay_01HXYZ789012",
  "status": "requires_action",
  "transactionId": "txn_7d8e42af",
  "requiresAction": {
    "type": "3ds_redirect",
    "redirectUrl": "https://3ds.useaxra.com/challenge?token=abc123"
  }
}
```

***

## Confirm a 3DS challenge

`POST /business/payment/confirm-3ds`

After a customer completes a 3DS challenge and is redirected back to your `returnUrl`, call this endpoint with the `transactionId` from the original charge response to finalize the payment.

<Note>
  Call this endpoint **after** the customer has completed the 3DS challenge in their browser. Calling it prematurely will result in a failed confirmation.
</Note>

### Request parameters

<ParamField body="transactionId" type="string" required>
  The `transactionId` returned in the original `POST /business/payment/charge` response when `status` was `"requires_action"`.
</ParamField>

### Response fields

<ResponseField name="paymentId" type="string">
  The Axra Pay payment identifier.
</ResponseField>

<ResponseField name="status" type="string">
  Final charge status: `"succeeded"` or `"failed"`.
</ResponseField>

<ResponseField name="success" type="boolean">
  `true` if authentication and charge completed successfully.
</ResponseField>

<ResponseField name="transactionId" type="string">
  Transaction reference, matching the value from the original charge.
</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.useaxra.com/api/v1/business/payment/confirm-3ds \
    --header 'x-api-key: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
      "transactionId": "txn_7d8e42af"
    }'
  ```

  ```javascript Node.js theme={null}
  const result = await fetch(
    'https://api.useaxra.com/api/v1/business/payment/confirm-3ds',
    {
      method: 'POST',
      headers: {
        'x-api-key': process.env.AXRA_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ transactionId: 'txn_7d8e42af' }),
    }
  );

  const confirmation = await result.json();

  if (confirmation.success) {
    // Await the payment.completed webhook to fulfil the order
    console.log('3DS confirmed:', confirmation.paymentId);
  } else {
    // Authentication failed — prompt the customer to try a different card
    console.error('3DS failed for transaction:', confirmation.transactionId);
  }
  ```
</CodeGroup>

```json theme={null}
{
  "paymentId": "pay_01HXYZ789012",
  "status": "succeeded",
  "success": true,
  "transactionId": "txn_7d8e42af"
}
```

***

## Get a payment

`GET /business/payment/:paymentId`

Retrieve the full details of a single payment by its ID. Use this endpoint to check the current status of a payment, inspect fee breakdowns, or confirm settlement.

### Path parameters

<ParamField path="paymentId" type="string" required>
  The unique Axra Pay payment ID (e.g., `pay_01HXYZ123456`).
</ParamField>

### Response fields

<ResponseField name="id" type="string">Unique payment identifier.</ResponseField>
<ResponseField name="businessId" type="string">Your Axra Pay business ID.</ResponseField>
<ResponseField name="amount" type="number">Charged amount in major currency units.</ResponseField>
<ResponseField name="currency" type="string">ISO 4217 currency code.</ResponseField>

<ResponseField name="status" type="string">
  Payment lifecycle status. `"COMPLETED"` means the payment has been confirmed and funds are allocated for settlement. Other possible values: `"PENDING"`, `"FAILED"`, `"REFUNDED"`.
</ResponseField>

<ResponseField name="type" type="string">Payment method type (e.g., `"card"`).</ResponseField>
<ResponseField name="transactionId" type="string">Axra transaction reference for the charge. Use when contacting support about a specific payment.</ResponseField>

<ResponseField name="settlementStatus" type="string">
  Settlement state: `"PENDING"`, `"SETTLED"`, or `"FAILED"`.
</ResponseField>

<ResponseField name="settledAt" type="string">
  ISO 8601 timestamp of when the funds were settled to your account. `null` if not yet settled.
</ResponseField>

<ResponseField name="metadata" type="object">
  All metadata key-value pairs submitted with the original charge, plus an Axra-generated `feeBreakdown` object showing processing fees.
</ResponseField>

<ResponseField name="createdAt" type="string">ISO 8601 creation timestamp.</ResponseField>
<ResponseField name="updatedAt" type="string">ISO 8601 timestamp of the last status change.</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.useaxra.com/api/v1/business/payment/pay_01HXYZ123456 \
    --header 'x-api-key: YOUR_API_KEY'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://api.useaxra.com/api/v1/business/payment/pay_01HXYZ123456',
    {
      headers: { 'x-api-key': process.env.AXRA_API_KEY },
    }
  );

  const payment = await response.json();
  console.log(payment.status, payment.settlementStatus);
  ```
</CodeGroup>

```json theme={null}
{
  "id": "pay_01HXYZ123456",
  "businessId": "biz_9f3a21c",
  "amount": 49.99,
  "currency": "usd",
  "status": "COMPLETED",
  "type": "card",
  "transactionId": "txn_9a3f21bc",
  "settlementStatus": "SETTLED",
  "settledAt": "2026-04-15T08:00:00.000Z",
  "metadata": {
    "orderId": "1042",
    "customerId": "cust_abc123",
    "feeBreakdown": {
      "processingFee": 1.74,
      "netAmount": 48.25,
      "currency": "usd"
    }
  },
  "createdAt": "2026-04-14T10:23:45.000Z",
  "updatedAt": "2026-04-14T10:24:01.000Z"
}
```

***

## List payments

`GET /business/payments`

Retrieve a paginated list of all payments for your business, ordered by creation time (most recent first).

### Query parameters

<ParamField query="page" type="number" default="1">
  Page number to retrieve. Starts at `1`.
</ParamField>

<ParamField query="limit" type="number" default="20">
  Number of payments per page. Maximum `100`.
</ParamField>

### Response fields

<ResponseField name="payments" type="array">
  Array of payment objects. Each object has the same structure as the [Get a payment](#get-a-payment) response.
</ResponseField>

<ResponseField name="pagination" type="object">
  <Expandable title="pagination properties">
    <ResponseField name="page" type="number">Current page number.</ResponseField>
    <ResponseField name="limit" type="number">Items per page.</ResponseField>
    <ResponseField name="total" type="number">Total number of payments across all pages.</ResponseField>
    <ResponseField name="totalPages" type="number">Total number of pages at the current limit.</ResponseField>
  </Expandable>
</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api.useaxra.com/api/v1/business/payments?page=1&limit=20' \
    --header 'x-api-key: YOUR_API_KEY'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://api.useaxra.com/api/v1/business/payments?page=1&limit=20',
    {
      headers: { 'x-api-key': process.env.AXRA_API_KEY },
    }
  );

  const { payments, pagination } = await response.json();
  console.log(`Showing ${payments.length} of ${pagination.total} payments`);
  ```
</CodeGroup>

```json theme={null}
{
  "payments": [
    {
      "id": "pay_01HXYZ123456",
      "amount": 49.99,
      "currency": "usd",
      "status": "COMPLETED",
      "transactionId": "txn_9a3f21bc",
      "createdAt": "2026-04-14T10:23:45.000Z"
    },
    {
      "id": "pay_01HWXY654321",
      "amount": 120.00,
      "currency": "ngn",
      "status": "PENDING",
      "transactionId": "txn_3c1b09da",
      "createdAt": "2026-04-13T18:11:02.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 348,
    "totalPages": 18
  }
}
```

***

## Refund a payment

`POST /business/payment/:paymentId/refund`

Issue a full refund for a completed payment. The refund is applied to the original card and the payment status is updated to `"REFUNDED"`.

<Warning>
  Refunds are only available within **180 days** of the original payment. Refund requests on payments older than 180 days will return a `400` error. You can only refund payments with a status of `"COMPLETED"`.
</Warning>

### Path parameters

<ParamField path="paymentId" type="string" required>
  The ID of the payment to refund.
</ParamField>

### Response fields

<ResponseField name="paymentId" type="string">The ID of the refunded payment.</ResponseField>
<ResponseField name="status" type="string">`"REFUNDED"` on success.</ResponseField>
<ResponseField name="refundId" type="string">Unique identifier for the refund record.</ResponseField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.useaxra.com/api/v1/business/payment/pay_01HXYZ123456/refund \
    --header 'x-api-key: YOUR_API_KEY'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://api.useaxra.com/api/v1/business/payment/pay_01HXYZ123456/refund',
    {
      method: 'POST',
      headers: { 'x-api-key': process.env.AXRA_API_KEY },
    }
  );

  const refund = await response.json();
  console.log('Refund ID:', refund.refundId);
  ```
</CodeGroup>

```json theme={null}
{
  "paymentId": "pay_01HXYZ123456",
  "status": "REFUNDED",
  "refundId": "ref_88bc4210"
}
```

### Error responses

| Status | Reason                                                           |
| ------ | ---------------------------------------------------------------- |
| `400`  | Payment is not in `COMPLETED` status, or is older than 180 days. |
| `404`  | No payment found with the given `paymentId`.                     |

***

## Idempotency

Axra Pay automatically deduplicates charge requests to prevent accidental double charges. If two requests are made within the same **one-minute window** with the same combination of:

* Business ID
* Card last four digits (or `savedTokenId`)
* `amount`
* `currency`

...the second request returns the existing payment record rather than creating a new charge. No additional action is required on your end — idempotency is applied automatically.

<Tip>
  If you need to charge the same card for the same amount multiple times in quick succession (e.g., split payments), wait at least 60 seconds between requests, or use a different card token.
</Tip>

***

## Rate limiting

| Context                  | Limit                                                                                                      |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| API requests             | Per-business limit — contact [support@useaxra.com](mailto:support@useaxra.com) for your current threshold. |
| Checkout charge attempts | Max **5 attempts** per session per **15-minute window**.                                                   |

When you exceed a rate limit, the API returns a `429 Too Many Requests` response. Implement exponential backoff before retrying.
