# Update invoice status / payment fields

`PATCH https://rebaar.in/api/v1/invoices/{id}`

Under the same rules as the app: an issued invoice never goes back to `Draft`; `Cancelled` is final; a `Draft` moves only to `Sent` (not while approvals are pending) or `Cancelled`. `amountPaid` (and `status: "Paid"`, which means "paid in full") RECORDS A PAYMENT for the difference — a confirmed receipt dated `paidAt` (default today, IST), visible and voidable in Rebaar, and fires `invoice.paid` when it settles the invoice. The same call twice records one payment. `amountPaid` can only go up: lowering it is 409 `amount_paid_decrease` (void the payment in Rebaar instead). Above the net total (`netTotalPaise`: the total after credit and debit notes), or with a `status` that disagrees with the amount, is 422. An invoice with an issued credit or debit note cannot be cancelled (409 `has_notes`). `Partial` cannot be set on its own — it follows the payments.

## Headers

| Header | Value |
|---|---|
| `Authorization` | `Bearer <your API key>` |
| `Content-Type` | `application/json` |

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The invoice id, as returned by the list endpoint. |

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `status` | string | no | one of "Draft", "Sent", "Paid", "Overdue", "Partial", "Unpaid", "Cancelled" |
| `amountPaid` | integer | no | Total amount received so far. Raising it records a payment for the difference; it cannot be lowered. in PAISE (rupees × 100). ₹1,500.00 = 150000. |
| `paidAt` | string (date) or null | no | Payment date as an ISO YYYY-MM-DD string. |
| `dueDate` | string (date) or null | no | Due date as an ISO YYYY-MM-DD string. |

## Example

```bash
curl -X PATCH 'https://rebaar.in/api/v1/invoices/f1a8c3d5-2e6b-4c9f-a017-3b5d8e2c4f96' \
  -H 'Authorization: Bearer ck_live_xxxxxxxx_…' \
  -H 'Content-Type: application/json' \
  -d '{
  "amountPaid": 5000000,
  "paidAt": "2026-10-15"
}'
```

## Responses

### 200

Updated. The body is `{ "ok": true }` — GET the record for its new state.

```json
{
  "ok": true
}
```

### 400

Validation failed. `error` is the first problem found; `field` names it when it is a location, GSTIN, email or delivery-address field.

```json
{
  "error": "no fields to update"
}
```

### 401

No `Authorization: Bearer …` header was sent. Or: The key is unknown, revoked or expired — or the company cannot use it.

`unauthenticated` — No `Authorization: Bearer …` header was sent.

```json
{
  "error": "unauthenticated",
  "message": "Authorization: Bearer <api_key> required"
}
```

`invalid_token` — The key is unknown, revoked or expired — or the company cannot use it.

```json
{
  "error": "invalid_token",
  "message": "API key not recognised, revoked, or expired"
}
```

### 402

The company is not on the Scale plan, which includes the API. Or: The subscription is not active (trial over, payment failed, cancelled, paused, locked or closing). `reason` says which.

`plan_required` — The company is not on the Scale plan, which includes the API.

```json
{
  "error": "plan_required",
  "message": "API access requires the Scale plan. This workspace is on Growth.",
  "currentPlan": "growth",
  "requiredPlan": "scale"
}
```

`access_expired` — The subscription is not active (trial over, payment failed, cancelled, paused, locked or closing). `reason` says which.

```json
{
  "error": "access_expired",
  "reason": "payment_failed",
  "message": "Workspace does not currently have an active Scale subscription."
}
```

### 403

The request came from an IP address that is not on the key's allowlist. Or: The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE.

`ip_not_allowed` — The request came from an IP address that is not on the key's allowlist.

```json
{
  "error": "ip_not_allowed",
  "message": "Source IP 203.0.113.7 is not on the key's allowlist."
}
```

`insufficient_scope` — The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE.

```json
{
  "error": "insufficient_scope",
  "message": "This API key does not have the 'write' scope.",
  "required": "write",
  "granted": [
    "read"
  ]
}
```

### 404

No such invoice in this workspace.

```json
{
  "error": "not_found"
}
```

### 409

Refused by an invoice rule — e.g. `amount_paid_decrease`, `paid_follows_payments`, `pending_exists`, `invoice_cancelled`, `invoice_not_issued`, `has_notes`, `invoice_changed`.

```json
{
  "error": "amount_paid_decrease",
  "message": "amountPaid can only go up through the API — each increase is recorded as a payment. To reduce what INV-2026-0042 shows as paid, void that payment in Rebaar."
}
```

### 422

`amount_exceeds_total` or `status_disagrees_with_amount`.

`amount_exceeds_total`

```json
{
  "error": "amount_exceeds_total",
  "message": "amountPaid cannot exceed the invoice total."
}
```

`status_disagrees_with_amount`

```json
{
  "error": "status_disagrees_with_amount",
  "message": "An amount paid of 5000000 on a total of 11800000 makes the invoice Partial, not Paid."
}
```

### 429

More than 120 requests in a minute with this key (30 with a sandbox key).

`rate_limited` — More than 120 requests in a minute with this key (30 with a sandbox key).

```json
{
  "error": "Too many requests. Please try again later."
}
```

### 500

Something failed on our side.

`internal` — Something failed on our side.

```json
{
  "error": "internal"
}
```

## Errors every call can return

| Status | Code | Meaning | What to do |
|---|---|---|---|
| 401 | `unauthenticated` | No `Authorization: Bearer …` header was sent. | Send your API key as `Authorization: Bearer ck_live_…` on every request. |
| 401 | `invalid_token` | The key is unknown, revoked or expired — or the company cannot use it. | Check you copied the whole key. Make a new one in Settings → API keys if it was revoked or has expired. |
| 403 | `ip_not_allowed` | The request came from an IP address that is not on the key's allowlist. | Call from an allowed address, or change the key's IP allowlist in Settings → API keys. |
| 402 | `plan_required` | The company is not on the Scale plan, which includes the API. | The Owner moves the company to Scale in Settings → Billing. |
| 402 | `access_expired` | The subscription is not active (trial over, payment failed, cancelled, paused, locked or closing). `reason` says which. | The Owner pays or renews in Settings → Billing; calls work again at once. |
| 429 | — | More than 120 requests in a minute with this key (30 with a sandbox key). | Wait the number of seconds in the `Retry-After` header. Every reply carries `RateLimit-Remaining`, so you can slow down before this happens. |
| 403 | `insufficient_scope` | The key does not carry the scope this method needs: `read` for GET, `write` for POST, PATCH and DELETE. | Make a key with the scope you need in Settings → API keys. |
| 500 | `internal` | Something failed on our side. | Retry with exponential backoff. If it keeps happening, email support@rebaar.in with the time of the call. |
