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

# Authentication

> Authenticate every request with your API key and secret

All ABCMPay API requests are authenticated with two headers, sent on every request:

```text theme={null}
X-Api-Key: your-public-key
X-Api-Secret: your-secret-key
```

You'll find both on your **API Keys** page in the merchant dashboard.

<Warning>
  Your secret key must never be exposed in client-side code (a mobile app, a browser script). Every ABCMPay endpoint is a server-to-server call — make requests from your backend only.
</Warning>

## Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://www.abcmpay.com/api/v1/wallet/balance" \
    -H "X-Api-Key: your-public-key" \
    -H "X-Api-Secret: your-secret-key"
  ```

  ```php PHP theme={null}
  <?php
  $curl = curl_init();
  curl_setopt_array($curl, [
      CURLOPT_URL => "https://www.abcmpay.com/api/v1/wallet/balance",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          "X-Api-Key: your-public-key",
          "X-Api-Secret: your-secret-key",
      ],
  ]);
  $response = curl_exec($curl);
  $data = json_decode($response, true);
  ```

  ```js Node.js theme={null}
  const response = await fetch("https://www.abcmpay.com/api/v1/wallet/balance", {
    headers: {
      "X-Api-Key": "your-public-key",
      "X-Api-Secret": "your-secret-key",
    },
  });
  const data = await response.json();
  ```
</CodeGroup>

## IP allowlisting (optional)

If you turn on IP allowlisting for your account, requests from any other IP are rejected with `IP_NOT_WHITELISTED` — even with a valid key and secret. Leave it off if you call the API from infrastructure with unpredictable outbound IPs (most serverless platforms).

## Errors

| Code                  | Meaning                                              |
| --------------------- | ---------------------------------------------------- |
| `MISSING_CREDENTIALS` | One or both headers weren't sent                     |
| `INVALID_CREDENTIALS` | The key/secret pair doesn't match an active account  |
| `IP_NOT_WHITELISTED`  | Allowlisting is on and this request's IP isn't on it |

See the full [error reference](/errors) for every code the API can return.

## Reference vs. transaction ID

Two different fields show up throughout the API and in webhooks, and **what `reference` means depends on which endpoint or event you're looking at** — it is not always yours, and it is not always unique. `transaction_id` (also returned as `order_no`) is the one field that's always ours, always unique, and always safe to key duplicate-detection off.

<ResponseField name="transaction_id / order_no" type="string">
  **Always ours, always unique.** ABCMPay's own internal identifier for the transaction, generated server-side on every transaction type. This is the value to store for reconciliation and to use as your idempotency/duplicate-detection key — it never repeats and never changes across webhook retries of the same payment.
</ResponseField>

<ResponseField name="reference" type="string">
  **Depends on the endpoint:**

  * **Payouts** — yours. An optional value you pass when creating a payout; if you don't pass one, we generate one. Unique either way.
  * **Collect Payments (checkout)** — ours. We generate this when the payment link is created; you never supply it.
  * **Virtual account payments** (the `payment.received` webhook) — the payer's bank transfer narration, exactly as their bank sent it. Free text, not chosen by you or by us, and **not unique** — two different payments can carry identical narration text. See [the webhook payload](/api-reference/webhooks#payload) for detail.

  Because its meaning changes by context, treat `reference` as a display/lookup convenience only, never as a duplicate-detection key — use `transaction_id` for that in every case.
</ResponseField>
