# Overview

> What the On Me API does, how a gift moves from order to recipient, and the core concepts every integration uses.


The On Me API lets your platform buy and send On Me gifts from your own code. You pick a category, choose an amount and a way to deliver it, and we take care of the rest: the gift reaches your recipient, they claim it, and they get a virtual card they can spend at that category's merchants.

Every gift is paid for from your organization's On Me balance, so there are no cards to charge and no checkout to build.

> **Warning: The API is in beta** The On Me API is still in beta, so any part of it can change at any time, including but not limited to request and response bodies, endpoints, headers and error codes. If something breaks or doesn't behave the way you expect, email [support@onme.com](mailto:support@onme.com).

## What you can do

- **Browse the catalog.** List every gift category with `GET /v1/categories`.
- **Send gifts.** Create an order of 1 to 10 gifts with `POST /v1/orders`. The whole order is paid for in one debit from your balance.
- **Deliver them your way.** We can email or text each gift for you, or hand you a link to share however you like.
- **Track what happens next.** Read orders and gifts back to see whether each one was sent, opened, claimed or failed.
- **Fix a failed delivery.** Point an undelivered email or text gift at a new address with `PUT /v1/gifts/{gift_id}/delivery`.

## Core concepts

- **Category**: what the gift is for, such as Coffee or Dining. The category decides where the recipient's card can be spent. You pass its `id` when you create a gift. Some categories, like Choice, let the recipient pick a category when they claim.
- **Order**: one purchase of 1 to 10 gifts, created with a single request and paid for with a single debit from your balance. Order IDs look like `o-…`.
- **Gift**: one gift for one recipient, with an amount, a category, a delivery method and, optionally, the names the recipient sees. Gift IDs look like `g-…`. Every gift belongs to exactly one order.
- **Recipient**: the person who receives the gift. They don't need an On Me account to receive it; they sign in or create one when they claim it.
- **Balance**: your organization's prepaid funds. You top it up in the business portal under **Billing**, and each order draws it down by the order's total.

## How a gift moves

Fund balance → POST /v1/orders → Gifts created → Delivered (email, SMS or link) → Recipient claims → Card issued. Delivery status moves sent → opened → claimed, or failed.

**Fund your balance.** Add funds in the [business portal](https://business.onme.com/billing). Your balance must cover an order's full total before we accept it.

**Create an order.** `POST /v1/orders` debits your balance and creates every gift in the order before it responds. Either the whole order goes through, or nothing happens. In rare cases we hit a problem after the debit: the order then comes back as `processing`, possibly with `gifts: []`, and its gifts are finished shortly after. See [when the status is processing](https://business.onme.com/docs/creating-orders#when-the-status-is-processing).

**We deliver each gift.** Email and SMS gifts go out straight away. Link gifts come back with a `delivery.link` URL for you to share.

**The recipient opens and claims it.** They open the gift, see who it's from, and claim it to their On Me account.

**We issue the card.** Claiming issues a virtual card that works at the category's merchants, or adds the funds to a card they already hold for that category.

You can follow each gift through `delivery.status`:

| `delivery.status` | What it means |
| --- | --- |
| `sent` | We sent the email or text, or created the link. This is where every gift starts. |
| `opened` | The recipient opened the gift. |
| `claimed` | The recipient claimed the gift and has a card to spend it on. |
| `failed` | We couldn't deliver the email or text. You can [send it somewhere else](https://business.onme.com/docs/delivery-methods). |

The status always shows the furthest point a gift has reached. A gift that failed, was re-sent and then claimed reads `claimed`.

## Requests and responses

Every request goes to `https://api.onme.com`, over HTTPS, and every path starts with `/v1`. Send request bodies as JSON with `Content-Type: application/json`; bodies are limited to 1 MB. Responses are always JSON.

Responses come in three shapes. There is no wrapper around them: what you see below is the entire body.

A single resource comes back as the object itself:

**200 GET /v1/orders/{order_id}**
```json
{
  "order_id": "o-bc1KeWwb90nw_0Jfspeu_oNs0EM7XcAKLQKS64QdQIo",
  "status": "processed",
  "gifts": [
    {
      "gift_id": "g-Q5-Vef_a-vnAqGgTVfBArnb8sjHOBCjz7Z7nm4Udlww",
      "amount": 2500,
      "category": "coffee",
      "delivery": { "method": "email", "status": "sent", "email": "sam@example.com" },
      "personalization": { "recipient_name": "Sam", "sender_name": "Acme" }
    }
  ]
}
```

A list comes back in a list object, with the items in `data`:

**200 GET /v1/gifts?limit=1**
```json
{
  "object": "list",
  "data": [
    {
      "gift_id": "g-Q5-Vef_a-vnAqGgTVfBArnb8sjHOBCjz7Z7nm4Udlww",
      "amount": 2500,
      "category": "coffee",
      "delivery": { "method": "email", "status": "opened", "email": "sam@example.com" },
      "personalization": { "recipient_name": "Sam", "sender_name": "Acme" }
    }
  ],
  "has_more": true
}
```

An error comes back as a single `error` object, with a non-2xx status:

**422 Unprocessable Entity**
```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "Invalid email address",
    "param": "gifts[0].delivery.email",
    "doc_url": "https://business.onme.com/docs/reference#errors/parameter_invalid",
    "request_id": "req-C4451Fpu_emD-fIdoqAQ8dKd9HqnoBiJUf-EvRdz0qM"
  }
}
```

Branch on `type` and `code`, never on `message`: the message is written for people and can change. When the error is about one field, `param` names it. Every code has its own entry in the [error reference](https://business.onme.com/docs/reference#errors), which `doc_url` links to directly.

> **Tip: An error body means nothing happened** If you get a response with an `error` object, we made no changes: no order, no charge, no gifts. That holds for every error code, including `internal_error`. If you get no response at all, or a gateway error without an `error` object, the outcome is unknown. Retry `POST /v1/orders` with the same `Idempotency-Key` and you get the original result. [Idempotency and retries](https://business.onme.com/docs/idempotency) covers this in full.

## Conventions

### Amounts

Amounts are integers in US cents. `2500` is $25.00. A gift can be worth between `1000` ($10) and `50000` ($500).

### IDs

IDs are opaque strings with a prefix that tells you what they are:

| Prefix | Identifies |
| --- | --- |
| `o-` | An order |
| `g-` | A gift |
| `req-` | A single API request |

Treat everything after the prefix as opaque: don't parse it or rely on its length. Category IDs are strings such as `coffee`.

### Pagination

`GET /v1/orders` and `GET /v1/gifts` return results newest first, one page at a time. They take three query parameters:

| Parameter | Description |
| --- | --- |
| `limit` | Page size, from 1 to 100. Defaults to 10. |
| `starting_after` | An ID from the current page. Returns the page after it. |
| `ending_before` | An ID from the current page. Returns the page before it. |

Send `starting_after` or `ending_before`, not both. When `has_more` is `true`, pass the last item's ID as `starting_after` to get the next page:

**cURL**

```bash
curl "https://api.onme.com/v1/orders?limit=25&starting_after=o-bc1KeWwb90nw_0Jfspeu_oNs0EM7XcAKLQKS64QdQIo" \
  -H "Authorization: Bearer $ONME_API_KEY"
```
**Node**

```js
const params = new URLSearchParams({
  limit: '25',
  starting_after: 'o-bc1KeWwb90nw_0Jfspeu_oNs0EM7XcAKLQKS64QdQIo',
})
const response = await fetch(`https://api.onme.com/v1/orders?${params}`, {
  headers: { Authorization: `Bearer ${process.env.ONME_API_KEY}` },
})
const page = await response.json()
console.log(page.data.length, page.has_more)
```
**Python**

```python
import os
import requests

response = requests.get(
    "https://api.onme.com/v1/orders",
    headers={"Authorization": f"Bearer {os.environ['ONME_API_KEY']}"},
    params={
        "limit": 25,
        "starting_after": "o-bc1KeWwb90nw_0Jfspeu_oNs0EM7XcAKLQKS64QdQIo",
    },
)
page = response.json()
print(len(page["data"]), page["has_more"])
```

A cursor that doesn't match one of your orders or gifts returns [`cursor_invalid`](https://business.onme.com/docs/reference#errors/cursor_invalid). `GET /v1/categories` returns the whole catalog in one response, so its `has_more` is always `false`.

### Request IDs

Every response, success or error, carries a `Request-Id` header, such as `req-C4451Fpu_emD-fIdoqAQ8dKd9HqnoBiJUf-EvRdz0qM`. Error bodies repeat it as `request_id`. Log it alongside your own records, and include it when you contact support@onme.com so we can find the exact request.

### Rate limits

Limits apply per API key, over a 60-second window:

| Scope | Limit |
| --- | --- |
| All endpoints | 120 requests per minute |
| `POST /v1/orders` | 30 requests per minute, which also count toward the 120 |

A separate limit of 300 requests per minute per IP address covers all traffic, including requests without a valid key.

Every response tells you where you stand, using the IETF `RateLimit` headers:

```http
RateLimit-Policy: 120;w=60
RateLimit: limit=120, remaining=117, reset=42
```

`remaining` is how many requests you have left in the window, and `reset` is the number of seconds until it resets. Responses from `POST /v1/orders` report its tighter limit. Go over a limit and you get a `429` with the [`rate_limit_exceeded`](https://business.onme.com/docs/reference#errors/rate_limit_exceeded) code and a `Retry-After` header. Wait that many seconds, then retry.

### Compatibility

While the API is in beta, any part of it can change, including in ways that break working integrations. We also add things without notice: new fields on responses, new endpoints, new optional parameters and new error codes. Build your integration so it ignores fields it doesn't recognize and handles a value it hasn't seen before, such as a new `delivery.status`, without crashing. If a change breaks your integration, email [support@onme.com](mailto:support@onme.com).

## Next steps

- [Authentication](https://business.onme.com/docs/authentication): get your API key and learn how to keep it safe.
- [Quickstart](https://business.onme.com/docs/quickstart): send your first gift in about five minutes.
- [Creating orders](https://business.onme.com/docs/creating-orders): everything a `POST /v1/orders` request can do.
- [API reference](https://business.onme.com/docs/reference): every endpoint, field and error code.
