# REST API

A small JSON API over HTTPS.

```text
https://api.openintents.io/v1
```

## Authentication

Send your [API key](/dashboard/api-keys) as a Bearer token. `oi_test_` keys
create test intents; `oi_live_` keys make real purchases.

```bash
curl https://api.openintents.io/v1/intents \
  -H "Authorization: Bearer $OPENINTENTS_API_KEY"
```

## Conventions

- Amounts are **integers in the smallest currency unit** (cents for USD).
- Timestamps are ISO 8601 in UTC.
- `POST` requests accept an `Idempotency-Key` header. Retrying with the same key
  returns the original intent instead of creating a second one. Use it for
  anything that spends money.
- Lists are cursor-paginated: pass `limit` (max 100) and the `next_cursor` from
  the previous page.

## Create an intent

<Endpoint method="POST" path="/v1/intents" />

| Parameter | Type | |
| --- | --- | --- |
| `intent` | string | **Required.** What to buy or book, in natural language. |
| `max_amount` | integer | Spending cap in cents. The intent fails rather than exceed it. |
| `currency` | string | ISO currency code. Defaults to the wallet's currency. |
| `context` | object | Structured details: addresses, traveler names, loyalty numbers. |
| `require_payment_link` | boolean | Always return a payment link at checkout instead of paying from the balance. |
| `metadata` | object | Up to 20 of your own key/value pairs. |

```bash
curl https://api.openintents.io/v1/intents \
  -H "Authorization: Bearer $OPENINTENTS_API_KEY" \
  -H "Idempotency-Key: 5b1f6c1e-coffee-0845" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "flat white, oat milk, pickup 8:45 at the Blue Bottle on 5th",
    "max_amount": 1000,
    "metadata": { "user": "u_42" }
  }'
```

```ts
const res = await fetch("https://api.openintents.io/v1/intents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPENINTENTS_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({ intent: "book an Uber to SFO at 7am", max_amount: 6000 }),
});
const intent = await res.json(); // { id: "int_…", status: "queued", … }
```

```python
import os, uuid, requests

intent = requests.post(
    "https://api.openintents.io/v1/intents",
    headers={
        "Authorization": f"Bearer {os.environ['OPENINTENTS_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"intent": "reorder my usual coffee beans on Amazon", "max_amount": 3000},
).json()
```

Returns the [intent object](/docs/intents#the-intent-object) with
`"status": "queued"` and HTTP `201`.

## Retrieve an intent

<Endpoint method="GET" path="/v1/intents/{id}" />

Returns the intent. Poll every 2 to 5 seconds, or use [webhooks](/docs/webhooks).

## List intents

<Endpoint method="GET" path="/v1/intents?status=completed&limit=20" />

```json
{
  "object": "list",
  "data": [{ "id": "int_7f3a2c", "status": "completed" }],
  "next_cursor": "int_7f29aa"
}
```

## Get the balance

<Endpoint method="GET" path="/v1/balance" />

```json
{ "object": "balance", "available": 24450, "currency": "usd", "monthly_limit": 100000, "spent_this_month": 67321 }
```

Intents pay from this balance when it covers the total and fee within your
limits; otherwise they return a [payment link](/docs/wallets#payment-links).

## Cancel an intent

<Endpoint method="POST" path="/v1/intents/{id}/cancel" />

Cancels an intent that has not paid yet (`queued`, `running` or
`requires_payment`). Cancelled intents are free.

## Errors

Errors use standard HTTP status codes and a consistent body:

```json
{
  "error": {
    "type": "invalid_request",
    "code": "max_amount_too_low",
    "message": "max_amount must be at least 50 (cents).",
    "param": "max_amount"
  }
}
```

| Status | `type` | When |
| --- | --- | --- |
| `400` | `invalid_request` | Missing or malformed parameters. |
| `401` | `authentication` | Missing, invalid or revoked API key. |
| `402` | `payment` | The payment for an intent was declined. |
| `404` | `not_found` | No such intent in this account. |
| `409` | `conflict` | Action not allowed in the intent's current status. |
| `429` | `rate_limit` | Too many requests; retry after the `Retry-After` header. |
| `5xx` | `api_error` | Our fault. Safe to retry with the same `Idempotency-Key`. |

Failures _during_ an intent (sold out, over budget) are not HTTP errors: the
intent ends in `failed` with an `error.code` such as `over_max_amount`,
`out_of_stock` or `merchant_unavailable`.
