# Create an invoice

`POST https://rebaar.in/api/v1/invoices/create`

All amounts are integers in paise (rupees × 100). The invoice is stored with its lines and the CGST + SGST or IGST split, so it can take an e-invoice IRN and credit notes. Send either `items` — the totals are computed for you, and a `subtotal`, `taxAmount` or `total` sent with them that differs is 400 `totals_mismatch` — or header totals only: then `subtotal` and `total` are required, `taxAmount` must be the GST of ONE rate (0, 1, 5, 12, 18 or 40 %) on `subtotal`, otherwise 400 `tax_not_one_rate` (send `items` for an invoice with several rates), and `total` must equal `subtotal + taxAmount` within 50 paise of rounding to the rupee (kept as the round-off), otherwise 400 `totals_mismatch`. Your `number` is kept as sent; one already used is 409 `invoice_number_exists`. A company with no GSTIN in Settings cannot charge GST (422 `gst_needs_gstin`). Optional `shipTo` when the goods go somewhere other than the client's address (e.g. a site): the e-invoice and e-way bill name it as the ship-to.

## Headers

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

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `number` | string | yes | at most 50 characters |
| `clientId` | string | no | A client of your company. |
| `projectId` | string | no | A project of your company. |
| `placeOfSupply` | string | no | 2-digit GST state code. Left out: the project site's state, else the client's.. pattern ^[0-9]{2}$ |
| `items` | array of object | no | The invoice lines. Left out: the header totals become one line.. at least 1 items. at most 500 items |
| `items[].description` | string | yes |  |
| `items[].hsnSac` | string | no |  |
| `items[].quantity` | number | yes | Up to 3 decimals; a 4th is 400.. minimum 0 |
| `items[].unit` | string | no |  |
| `items[].ratePaise` | integer | yes | Rate per unit in PAISE (rupees × 100). ₹1,500.00 = 150000. |
| `items[].gstRate` | integer | yes | GST %.. one of 0, 1, 5, 12, 18, 40 |
| `items[].cessRate` | number | no | minimum 0 |
| `subtotal` | integer | no | Pre-tax subtotal (required without items) in PAISE (rupees × 100). ₹1,500.00 = 150000. |
| `taxAmount` | integer | no | GST amount in PAISE (rupees × 100). ₹1,500.00 = 150000.. default 0 |
| `total` | integer | no | Grand total (required without items) in PAISE (rupees × 100). ₹1,500.00 = 150000. |
| `status` | string | no | one of "Draft", "Sent". default "Draft" |
| `issueDate` | string (date) | no | Issue date as an ISO YYYY-MM-DD string. Left out: today (IST). |
| `dueDate` | string (date) | no | Due date as an ISO YYYY-MM-DD string. |
| `shipTo` | object or null | no | Where the goods go, when not to the client. Omit (or null) to deliver to the client. |
| `shipTo.address` | string | yes | Delivery address. No double quotes.. at least 3 characters. at most 100 characters |
| `shipTo.place` | string | yes | Delivery city or place. No double quotes.. at least 3 characters. at most 50 characters |
| `shipTo.pincode` | string | yes | 6 digits, not starting with 0.. pattern ^[1-9][0-9]{5}$ |
| `shipTo.stateCode` | string or null | no | 2-digit GST state code. Left out: worked out from the PIN; a state the PIN is not in is 400.. pattern ^[0-9]{2}$ |

## Example

```bash
curl -X POST 'https://rebaar.in/api/v1/invoices/create' \
  -H 'Authorization: Bearer ck_live_xxxxxxxx_…' \
  -H 'Content-Type: application/json' \
  -d '{
  "number": "INV-2026-0042",
  "clientId": "7d3f1c2a-5b8e-4f0a-9c61-2e4b7a9d0c13",
  "projectId": "casa-verde",
  "status": "Draft",
  "issueDate": "2026-10-01",
  "dueDate": "2026-10-31",
  "items": [
    {
      "description": "RCC work, first floor slab",
      "hsnSac": "995411",
      "quantity": 1,
      "unit": "lot",
      "ratePaise": 10000000,
      "gstRate": 18
    }
  ],
  "shipTo": {
    "address": "14, 2nd Cross, JP Nagar 7th Phase",
    "place": "Bengaluru",
    "pincode": "560076"
  }
}'
```

## Responses

### 201

Created. The new invoice's id and your number.

```json
{
  "id": "f1a8c3d5-2e6b-4c9f-a017-3b5d8e2c4f96",
  "number": "INV-2026-0042"
}
```

### 400

Validation failed, or the totals do not add up.

`totals_mismatch`

```json
{
  "error": "totals_mismatch",
  "field": "total",
  "message": "The totals sent do not match the items: subtotal 10000000, taxAmount 1800000, total 11800000. Omit them to use the computed figures."
}
```

`tax_not_one_rate`

```json
{
  "error": "tax_not_one_rate",
  "field": "taxAmount",
  "message": "taxAmount is not the GST of one rate (0, 1, 5, 12, 18, 40 %) on the subtotal. Send the invoice as line items, each with its own GST rate."
}
```

`validation`

```json
{
  "error": "subtotal is required when items is not sent"
}
```

### 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

With a sandbox key: the practice company already holds 10,000 invoices (`sandbox_limit`) — reset the sandbox in Settings → API keys. Or the plan / subscription does not allow the API.

`sandbox_limit`

```json
{
  "error": "sandbox_limit",
  "message": "The sandbox holds at most 10,000 invoices. Reset the sandbox in Settings → API keys.",
  "resource": "invoices",
  "used": 10000,
  "limit": 10000
}
```

`plan_required`

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

`access_expired`

```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

`clientId` or `projectId` is not one of your company's.

`client_not_found`

```json
{
  "error": "client_not_found"
}
```

`project_not_found`

```json
{
  "error": "project_not_found"
}
```

### 409

That invoice number is already used in your company.

```json
{
  "error": "invoice_number_exists"
}
```

### 422

Your company has no GSTIN, so the invoice cannot charge GST. `code` is `gst_needs_gstin`; `field` names the line.

```json
{
  "error": "Your company has no GSTIN in Settings → Company, so this bill cannot charge GST. Set GST to 0 %, or add your GSTIN first.",
  "code": "gst_needs_gstin",
  "field": "items.0.gstRate"
}
```

### 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. |
