# Authentication

API keys, environments, the base URL, idempotency and rate limits — everything every paylod request has in common.

## Base URL

Every endpoint lives under `/functions/v1`.

```text title="Base URL"
https://paylod.dev/functions/v1
```

## API keys

Authenticate every request with a paylod API key as a bearer token.

```http title="Header"
Authorization: Bearer mp_live_YOUR_API_KEY
```

Create keys on an application's **API Keys** tab. A key is shown once, and it encodes two things:

- **Which application** the request runs against — so you never send a shortcode.
- **Which environment** it runs in — so you never send a `?sandbox=true` flag.

| Prefix | Environment | Money |
| --- | --- | --- |
| `mp_test_…` | Sandbox | Simulated. Nothing settles. |
| `mp_live_…` | Production | Real. Settles to your till. |

Your Daraja consumer key, secret and passkey stay in encrypted storage server-side. They never travel over the wire, and no endpoint reads them back.

> [!warn] Keys are server-side secrets. A key embedded in a browser or a mobile app is fully compromised — treat it as public and rotate it. See [Secure integration](/docs/guides/security).

## Idempotency

`POST /collect` accepts an `Idempotency-Key` header (or an `idempotencyKey` field in the body). A retry with **the same key and an identical body** replays the original response instead of firing a second STK Push — which makes network retries and double-clicks safe.

```bash title="Idempotent collect"
curl -X POST https://paylod.dev/functions/v1/collect \
  -H "Authorization: Bearer mp_live_YOUR_API_KEY" \
  -H "Idempotency-Key: order_2041" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 10, "phone": "254712345678" }'
```

Reuse the same key with a **different** body and the request is rejected with `409` — a guard against accidentally recycling a key across two different orders. Use one key per logical order.

## Rate limits

`POST /collect` is rate-limited per API key and per phone number. Expect `429 Too Many Requests` under bursts, and back off — retrying with the same `Idempotency-Key` after a short delay is always safe.

## Errors

paylod returns conventional HTTP status codes.

| Status | Meaning |
| --- | --- |
| `400` | Malformed JSON, or Daraja credentials are not configured for this application and environment. |
| `401` | Missing or invalid API key. |
| `404` | The resource does not exist, or belongs to another application. |
| `409` | Idempotency key reused with a different body. |
| `422` | Body failed validation (e.g. `amount` ≤ 0, unparseable phone). |
| `429` | Rate limited. Back off and retry. |
| `502` | Daraja rejected the request. The upstream error is passed through. |

Beyond HTTP status, M-Pesa has its own result codes — `1032` (customer cancelled), `1037` (timeout), `2001` (wrong PIN). Every one of them is decoded in the [error reference](/docs/errors).
