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

# Create order

> Create an order and send the invitation.

Headers: `Authorization` and `Content-Type: application/json` (required), and `Idempotency-Key` (optional, recommended).

> [!NOTE]
>
> The `Idempotency-Key` header is optional, but we strongly recommend sending it on every `POST /orders` so the same order is never created twice. When a request times out or the connection drops you cannot know whether the order was created. Repeat the request with the same key and the same email: you get the same order back (header `Idempotent-Replayed: true`) and are never charged twice. Keys are remembered for 24 hours. Reusing a key with a different email returns `422 idempotency_conflict`.

> [!WARNING]
>
> Without the header the request still works and the duplicate-email check (`409 duplicate_order`) still applies, but we cannot recognise a retry as the same request. In particular, retrying a timed-out request that has `allow_duplicate: true` and no key can create and charge a second order.

## Headers

**Idempotency-Key** `string`

Optional, recommended. 8-64 characters (letters, digits, `_ . : -`), for example a UUID. Prevents the same order from being created twice when you retry. Store it with your order so a retry reuses it.

## Body

**email** `string` *required*

Buyer's email. One address per order.

**name** `string`

Optional. Used in the greeting (max 60 chars).

**lang** `string` (default: en)

`"en"` | `"id"`. Language of the email sent to the buyer: English or Indonesian. Default `"en"`. Any other value is rejected with `400`. This is independent of your account currency.

**reference** `string`

Your own order reference (max 64). Returned in responses and webhooks; searchable via the list endpoint.

**allow\_duplicate** `boolean`

`true` or `false` only. By default an email that received an invitation in the last 30 days is refused with `409`. Set true to send again (charged). Re-sending is only allowed when the earlier invitation was one of your own orders; otherwise it is still refused with `409`.

## Response

Returns the [order object](https://docs.balaipedia.com/api-reference/get-order.md#the-order-object) with status `201 Created`, plus `balance`: your balance after the charge.

## Rejected by validation, `422` (nothing charged)

```json theme={null}
{"error":{"code":"email_rejected","message":"The email address was rejected by validation. No credit was charged.","reason":"mailbox_full","note":"The recipient's mailbox is full.","request_id":"req_..."}}
```

`reason` is a stable code you can branch on; `note` is a short human-readable explanation you can log or show.

| reason | Meaning |
| - | - |
| `invalid_format` | Not a valid email format. |
| `typo_domain` | Likely typo in the domain; `suggestion` may contain the corrected address. |
| `domain_no_mail` | Domain does not exist or does not accept email. |
| `mailbox_not_found` | The mailbox does not exist. |
| `mailbox_full` | The recipient's inbox is full. |
| `disposable_email` | Temporary / disposable email service. |
| `address_undeliverable` | This address previously failed or is on our block list. |
| `high_risk` | Flagged as risky (spam trap, abuse, do-not-mail). |
| `invalid_email` | Other validation failure. |

## Other failure responses

Every failure uses the same body, `{"error":{"code","message","request_id"}}`. Branch on `code`, not on `message`. A failed request never creates an order. Credit is charged only on `201`.

| HTTP | code | Example body | What to do |
| - | - | - | - |
| 400 | `invalid_request` | `{"error":{"code":"invalid_request","message":"Only one email address per order.","request_id":"req_..."}}` | One email per request: no bulk, no list, and no comma-, semicolon- or space-separated addresses. Other messages: `"email" is required.`, `"email" must be a string.`, `"lang" must be "id" or "en".`, `"allow_duplicate" must be a boolean.`, `Request body must be a JSON object.`, `Idempotency-Key must be 8-64 chars (letters, digits, _ . : -).` Fix the request, do not retry unchanged. |
| 400 | `invalid_json` | `{"error":{"code":"invalid_json","message":"Request body must be valid JSON.","request_id":"req_..."}}` | Send valid JSON with `Content-Type: application/json`. |
| 401 | `unauthorized` | `{"error":{"code":"unauthorized","message":"Missing or invalid API key.","request_id":"req_..."}}` | Check the `Authorization: Bearer ...` header and the key. |
| 402 | `insufficient_credit` | `{"error":{"code":"insufficient_credit","message":"Your credit balance is too low for this order.","balance":0,"required":0.15,"currency":"USD","request_id":"req_..."}}` | Top up, then send the request again. Nothing was charged. |
| 409 | `duplicate_order` | `{"error":{"code":"duplicate_order","message":"This email already received an invitation recently. Set \"allow_duplicate\": true to send again (charged).","request_id":"req_..."}}` | The same email was ordered recently. Send `"allow_duplicate": true` only if you really want to send (and be charged) again. Some duplicates cannot be re-sent at all and need support. |
| 409 | `request_in_progress` | `{"error":{"code":"request_in_progress","message":"A request with this Idempotency-Key is still being processed.","request_id":"req_..."}}` | Wait a few seconds and send the same request with the same key again. |
| 422 | `email_rejected` | see above | Not charged. Ask the buyer for a correct address. |
| 422 | `idempotency_conflict` | `{"error":{"code":"idempotency_conflict","message":"This Idempotency-Key was already used with a different email.","request_id":"req_..."}}` | Use a new key for a different email. |
| 429 | `rate_limited` | `{"error":{"code":"rate_limited","message":"Too many requests. Slow down.","request_id":"req_..."}}` | Wait for the number of seconds in the `Retry-After` header, then retry. |
| 503 | `temporarily_unavailable` | `{"error":{"code":"temporarily_unavailable","message":"We could not process this order right now. No credit was charged. Please retry shortly.","request_id":"req_..."}}` | Header `Retry-After: 30`. Retry after that, with the same `Idempotency-Key` if you sent one. |

Other statuses (`403`, `404`, `405`, `413`, `500`) are listed under [Errors](https://docs.balaipedia.com/errors.md).

```bash curl theme={null}
curl -X POST https://orders.balaipedia.com/v1/orders \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2f7e-9a4b-4d0e-8f3a-1b2c3d4e5f60" \
  -d '{"email":"buyer@gmail.com","name":"Budi","lang":"en","reference":"ORD-10293"}'
```

```javascript Node.js theme={null}
// Node 18+
const key = require("crypto").randomUUID();   // store it with your order so a retry reuses it
const res = await fetch("https://orders.balaipedia.com/v1/orders", {
  method: "POST",
  headers: {
    "Authorization": "Bearer " + process.env.BP_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": key
  },
  body: JSON.stringify({ email: "buyer@gmail.com", name: "Budi", lang: "en", reference: "ORD-10293" })
});
const text = await res.text();
let data = null;
try { data = JSON.parse(text); } catch (e) {}   // a 5xx from the platform may not be JSON
if (res.status === 201) { /* save data.id with your own order */ }
else if (res.status >= 500 || data === null) { /* retry later with the SAME key */ }
else { /* data.error.code tells you what to fix */ }
```

```php PHP theme={null}
<?php
$ch = curl_init("https://orders.balaipedia.com/v1/orders");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("BP_API_KEY"),
    "Content-Type: application/json",
    "Idempotency-Key: " . $key,   // e.g. a UUID you stored with your order
  ],
  CURLOPT_POSTFIELDS => json_encode(["email" => "buyer@gmail.com", "name" => "Budi", "lang" => "en", "reference" => "ORD-10293"]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode((string)$body, true);   // null when the body is not JSON (retry with the same key)
```

```python Python theme={null}
import os, uuid, requests

key = str(uuid.uuid4())   # store it with your order so a retry reuses it
r = requests.post(
    "https://orders.balaipedia.com/v1/orders",
    headers={"Authorization": "Bearer " + os.environ["BP_API_KEY"], "Idempotency-Key": key},
    json={"email": "buyer@gmail.com", "name": "Budi", "lang": "en", "reference": "ORD-10293"},
    timeout=30,
)
try: data = r.json()
except ValueError: data = None   # a 5xx from the platform may not be JSON: retry with the same key
```

```json 201 Created theme={null}
{
  "id": "BP-7K3M9QXA",
  "object": "order",
  "email": "buyer@gmail.com",
  "reference": "ORD-10293",
  "status": "processing",
  "note": null,
  "created_at": "2026-11-12T03:10:00.000Z",
  "completed_at": null,
  "expires_at": "2026-12-12",
  "amount": 0.15,
  "currency": "USD",
  "refunded": false,
  "balance": 42.2
}
```

```json 422 Rejected theme={null}
{"error":{"code":"email_rejected","message":"The email address was rejected by validation. No credit was charged.","reason":"mailbox_full","note":"The recipient's mailbox is full.","request_id":"req_..."}}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.