# Add an invoice

`POST /api/v1/invoices`

Adds a payable or a receivable, checked by the rules of the console's invoice form, and recorded in the ledger as `create_invoice` with `via: "api"` and the key's id. It is added as the key's issuer's: if the agent holds it, the issuer cannot approve it, unless they are the workspace's only approver.

The agent decides a payable as one typed in, with every guardrail and the workspace's limits, usually within a minute. The API never approves or pays. A `counterpartyId` the workspace does not hold, including another workspace's, answers `400`.

Send a [workspace API key](https://www.vestiarion.xyz/docs/get-started/authentication) with read and write access as `Authorization: Bearer <key>`. A read-only key gets `403`.

## Parameters

| Name | In | Type | Required | Default | Allowed values | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | Optional | — | — | Makes a retry safe: up to 255 printable ASCII characters, unique to the record being added, such as its id in your own system. A repeat with the same key and the same body within 24 hours gets the first answer back, with `Idempotent-Replayed: true`, and adds nothing. The same key with a different body answers `409`. |

## Request body

Example, `application/json`:

```json
{
  "counterpartyId": "6b361405-cfda-4400-a286-364b561911ce",
  "amount": "0.10",
  "dueDate": "2026-10-03",
  "poReference": "PO-API-1",
  "goodsReceived": true
}
```

**Fields**

- `direction` (string, optional): `payable`, a bill the business pays, which is the default, or `receivable`, one it is owed. One of `payable`, `receivable`.
- `counterpartyId` (string, required): The counterparty's `id`, from `GET /api/v1/counterparties` or from the answer that added it.
- `amount` (string | number, required): What it bills, in `currency`, with at most 6 decimal places. A decimal string such as `"1250.50"` keeps it exact; a number is read the same way.
- `currency` (string, optional): `USDC`, the default, or `EURC`. One of `USDC`, `EURC`.
- `dueDate` (string, required): The day it is due, as `YYYY-MM-DD`.
- `memo` (string, optional): What it is for, up to 280 characters.
- `poReference` (string, optional): The purchase order it bills against, up to 100 characters.
- `goodsReceived` (boolean, optional): Whether what it bills for has arrived. Defaults to `false`. Without it, or without `poReference`, the agent asks for the missing detail instead of paying.
- `earlyPayDiscount` (object, optional): A discount for paying by `deadline`. The agent weighs it against what the cash would earn in the reserve until `dueDate`.
  - `percent` (string | number, required): The percent off, greater than 0 and less than 100, with at most 2 decimal places.
  - `deadline` (string, required): The last day it applies, as `YYYY-MM-DD`, on or before `dueDate`.

## Code samples

```bash
curl "https://www.vestiarion.xyz/api/v1/invoices" \
  -X POST \
  -H "Authorization: Bearer $VESTIARION_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: billing-inv-2026-0042" \
  -d '{"counterpartyId":"6b361405-cfda-4400-a286-364b561911ce","amount":"0.10","dueDate":"2026-10-03","poReference":"PO-API-1","goodsReceived":true}'
```

## Response

Example, `201` `application/json`:

```json
{
  "data": {
    "id": "1f96fd0b-71de-41bf-b088-779655ea6df4",
    "direction": "payable",
    "status": "pending",
    "amount": 0.1,
    "currency": "USDC",
    "memo": null,
    "poReference": "PO-API-1",
    "goodsReceived": true,
    "dueDate": "2026-10-03T12:00:00+00:00",
    "scheduledFor": null,
    "earlyPayDiscount": null,
    "decidedAt": null,
    "settledAt": null,
    "escalatedAt": null,
    "agentReasoning": null,
    "txHash": null,
    "paidAmount": null,
    "counterparty": {
      "id": "6b361405-cfda-4400-a286-364b561911ce",
      "name": "API Test Vendor",
      "riskLevel": "clear"
    },
    "createdAt": "2026-10-03T10:16:16.374931+00:00"
  }
}
```

**Fields**

- `data` (object, required): An invoice in the payable or receivable book, with the agent's reasoning.
  - `id` (string, required)
  - `direction` (string, required) One of `payable`, `receivable`.
  - `status` (string, required)
  - `amount` (number, required)
  - `currency` (string, required): USDC or EURC: what `amount` is in, and what a payable is paid in. A EURC payable is checked against the counterparty's USDC limit at a quoted rate.
  - `memo` (string, nullable, required)
  - `poReference` (string, nullable, required)
  - `goodsReceived` (boolean, required)
  - `dueDate` (string, required)
  - `scheduledFor` (string, nullable, required): ISO timestamp the agent has committed to pay this on, once scheduled; else null.
  - `earlyPayDiscount` (object, nullable, required): The early-payment discount this invoice carries, if any: the percent off and the deadline's ISO timestamp.
    - `percent` (number, required)
    - `deadline` (string, required)
  - `decidedAt` (string, nullable, required)
  - `settledAt` (string, nullable, required)
  - `escalatedAt` (string, nullable, required)
  - `agentReasoning` (string, nullable, required): Why the agent ruled as it did, verbatim from the decision.
  - `txHash` (string, nullable, required): An on-chain hash once the payment settled, else null: on Arc testnet, or for a payout from a Gateway balance the mint on the payee's chain.
  - `paidAmount` (number, nullable, required): What actually left once this invoice was paid; null otherwise, even while a submitted transfer already carries an amount.
  - `counterparty` (object, nullable, required)
    - `id` (string, required)
    - `name` (string, required)
    - `riskLevel` (string, required)
  - `createdAt` (string, required)

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `invalid_request` | An invalid `limit` or `cursor`, a filter value outside its allowed values, or a request body that does not validate. The message names the parameter or the field, and lists the accepted values. |
| 401 | `unauthorized` | No key, or a malformed, unknown or revoked one: "A valid API key is required." |
| 403 | `forbidden` | The key's scopes do not cover this route: "This key cannot do that." Or, on a write, the person who created the key can no longer add records: "This key's issuer can no longer add records in this workspace." |
| 409 | `conflict` | The `Idempotency-Key` was already used for a different request, or the first request with it is still being handled. |
| 429 | `rate_limited` | Too many requests; wait as long as `Retry-After` says before trying again. |
| 500 | `internal` | An unexpected server error. Implementation details are not exposed. |
