Invoices API
Every Invoices action (14) and data model (4): endpoints, scopes, input, preconditions, events and examples.
Numbered invoices and credit notes, with tax on every line.
Module code invoices · 14 actions · 4 data models · 12 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: invoices (everything), invoices:read (read only) or invoices:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
invoice_attachments | invoice_attachment | /api/v1/invoice_attachments | |||
invoice_events | invoice_event | /api/v1/invoice_events | |||
invoice_lines | invoice_line | /api/v1/invoice_lines | |||
invoices | invoice | /api/v1/invoices |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
invoices.add_note | invoices:write | Add a note |
invoices.attach_file | invoices:write | Attach a file |
invoices.create_draft | invoices:write | Start an invoice |
invoices.create_from_agreement | invoices:write | Invoice an agreement |
invoices.detach_file | invoices:write | Detach a file |
invoices.issue | invoices:write | Issue an invoice |
invoices.issue_credit_note | invoices:manage | Issue a credit note |
invoices.mark_paid | invoices:write | Mark as paid |
invoices.mark_sent | invoices:write | Mark as sent |
invoices.raise_notices | invoices:write | Raise invoice reminders |
invoices.send | invoices:write | Send by email |
invoices.settle_from_agreement | invoices:write | Settle from the agreement |
invoices.update_draft | invoices:write | Edit a draft invoice |
invoices.void | invoices:manage | Void an invoice |
invoices.add_note
Add a note — A line in the invoice’s own history — Saleor’s note on the invoice timeline.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoice_events/actions/add_note- Canonical address:
POST /api/v1/actions/invoices.add_note
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Invoice · id of invoice |
message | string | yes | Note · max 4000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
Events it publishes: invoices.note_added
What it writes: invoice_event (insert) — Records the note
curl -X POST https://app.getera.io/api/v1/invoice_events/actions/add_note \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"INVOICE_ID","message":"Note"}'{
"ok": true,
"result": "…",
"events": [
"invoices.note_added"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoiceEvents.addNote({
id: "INVOICE_ID",
message: "Note"
})era invoice_events add_note \
--id "INVOICE_ID" \
--message "Note"{
"name": "era__invoices__add_note",
"arguments": {
"id": "INVOICE_ID",
"message": "Note"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found · 429 slow down
invoices.attach_file
Attach a file — Links an uploaded file to the invoice. The file itself is the shell’s.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoice_attachments/actions/attach_file- Canonical address:
POST /api/v1/actions/invoices.attach_file
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Invoice · id of invoice |
fileId | uuid | yes | File · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
attachment_file_not_found | The file must exist. |
Events it publishes: invoices.attachments_changed
What it writes: invoice_attachment (insert) — Links the file; invoice_event (insert) — Records it
curl -X POST https://app.getera.io/api/v1/invoice_attachments/actions/attach_file \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"INVOICE_ID","fileId":"FILE_ID"}'{
"ok": true,
"result": "…",
"events": [
"invoices.attachments_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoiceAttachments.attachFile({
id: "INVOICE_ID",
fileId: "FILE_ID"
})era invoice_attachments attach_file \
--id "INVOICE_ID" \
--file-id "FILE_ID"{
"name": "era__invoices__attach_file",
"arguments": {
"id": "INVOICE_ID",
"fileId": "FILE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, attachment_file_not_found · 429 slow down
invoices.create_draft
Start an invoice — A draft invoice with its lines, each carrying a frozen tax snapshot.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoices/actions/create_draft- Canonical address:
POST /api/v1/actions/invoices.create_draft
Input
| Field | Type | Required | Notes |
|---|---|---|---|
customerId | uuid | yes | Billed to · id of customer |
agreementId | uuid | Agreement · id of agreement | |
currency | string | Currency · max 3 | |
taxCountry | string | Tax country · max 2 | |
dueDate | date | Due | |
notes | string | Notes · max 4000 | |
lines | object / array | yes | Lines |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
customer_not_found | The person must exist. |
customer_archived | An archived person is not invoiced. |
invalid_currency | The currency must be an ISO-4217 code. |
agreement_not_found | A named agreement must exist. |
invoice_needs_lines | An invoice needs at least one line. |
invalid_invoice_lines | Every line needs a description, a positive quantity and a price. |
Events it publishes: invoices.draft_created
What it writes: invoice (insert) — Opens the draft; invoice_line (insert) — Writes its lines with their tax snapshot; invoice_event (insert) — Records that it was created
curl -X POST https://app.getera.io/api/v1/invoices/actions/create_draft \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"customerId":"CUSTOMER_ID","lines":{}}'{
"ok": true,
"result": "…",
"events": [
"invoices.draft_created"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.createDraft({
customerId: "CUSTOMER_ID",
lines: {}
})era invoices create_draft --data '{"customerId":"CUSTOMER_ID","lines":{}}'{
"name": "era__invoices__create_draft",
"arguments": {
"customerId": "CUSTOMER_ID",
"lines": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found, customer_archived, invalid_currency, agreement_not_found, invoice_needs_lines, invalid_invoice_lines · 429 slow down
invoices.create_from_agreement
Invoice an agreement — A draft invoice from an agreement’s lines, discount and currency.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoices/actions/create_from_agreement- Canonical address:
POST /api/v1/actions/invoices.create_from_agreement
Input
| Field | Type | Required | Notes |
|---|---|---|---|
agreementId | uuid | yes | Agreement · id of agreement |
taxCountry | string | Tax country · max 2 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
agreement_not_found | The agreement must exist. |
agreement_not_invoiceable | A cancelled agreement is not invoiced. |
agreement_already_invoiced | An agreement has one live invoice at a time. |
Events it publishes: invoices.draft_created
What it writes: invoice (insert) — Opens the draft; invoice_line (insert) — Copies the lines; invoice_event (insert) — Records that it was created
curl -X POST https://app.getera.io/api/v1/invoices/actions/create_from_agreement \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"agreementId":"AGREEMENT_ID"}'{
"ok": true,
"result": "…",
"events": [
"invoices.draft_created"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.createFromAgreement({
agreementId: "AGREEMENT_ID"
})era invoices create_from_agreement \
--agreement-id "AGREEMENT_ID"{
"name": "era__invoices__create_from_agreement",
"arguments": {
"agreementId": "AGREEMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, agreement_not_invoiceable, agreement_already_invoiced · 429 slow down
invoices.detach_file
Detach a file — Unlinks a file from the invoice; the file stays in Files.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoice_events/actions/detach_file- Canonical address:
POST /api/v1/actions/invoices.detach_file
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Invoice · id of invoice |
fileId | uuid | yes | File · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
attachment_not_found | The file must be attached. |
Events it publishes: invoices.attachments_changed
What it writes: invoice_attachment (delete) — Unlinks the file; invoice_event (insert) — Records it
curl -X POST https://app.getera.io/api/v1/invoice_events/actions/detach_file \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"INVOICE_ID","fileId":"FILE_ID"}'{
"ok": true,
"result": "…",
"events": [
"invoices.attachments_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoiceEvents.detachFile({
id: "INVOICE_ID",
fileId: "FILE_ID"
})era invoice_events detach_file \
--id "INVOICE_ID" \
--file-id "FILE_ID"{
"name": "era__invoices__detach_file",
"arguments": {
"id": "INVOICE_ID",
"fileId": "FILE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, attachment_not_found · 429 slow down
invoices.issue
Issue an invoice — Gives the draft its number from the workspace’s own series and freezes who it is billed to.
Needs invoices:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/invoices/{id}/actions/issue- Canonical address:
POST /api/v1/actions/invoices.issue
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
issueDate | date | Issue date | |
dueDate | date | Due |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_draft | Only a draft is issued. |
invoice_needs_lines | An invoice needs at least one line. |
Events it publishes: invoices.issued
What it writes: invoice (update) — Numbers and issues it; invoice_event (insert) — Records the issue
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/issue \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"issueDate":"2026-11-02"}'{
"ok": true,
"result": "…",
"events": [
"invoices.issued"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.issue("INVOICE_ID", {
issueDate: "2026-11-02"
})era invoices issue INVOICE_ID \
--issue-date "2026-11-02"{
"name": "era__invoices__issue",
"arguments": {
"id": "INVOICE_ID",
"issueDate": "2026-11-02"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_draft, invoice_needs_lines · 429 slow down
invoices.issue_credit_note
Issue a credit note — Credits an issued or paid invoice in full, as its own numbered document.
Needs invoices:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/invoices/{id}/actions/issue_credit_note- Canonical address:
POST /api/v1/actions/invoices.issue_credit_note
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
reason | string | yes | Reason · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_creditable | Only an issued or paid invoice is credited. |
invoice_already_credited | An invoice is credited once. |
Events it publishes: invoices.credit_note_issued
What it writes: invoice (insert) — Writes the credit note; invoice_line (insert) — Copies the credited lines; invoice_event (insert) — Records it on both documents
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/issue_credit_note \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"reason":"Reason"}'{
"ok": true,
"result": "…",
"events": [
"invoices.credit_note_issued"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.issueCreditNote("INVOICE_ID", {
reason: "Reason"
})era invoices issue_credit_note INVOICE_ID \
--reason "Reason"{
"name": "era__invoices__issue_credit_note",
"arguments": {
"id": "INVOICE_ID",
"reason": "Reason"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_creditable, invoice_already_credited · 429 slow down
invoices.mark_paid
Mark as paid — Settles an issued invoice by hand, when the money arrived outside finance.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoices/{id}/actions/mark_paid- Canonical address:
POST /api/v1/actions/invoices.mark_paid
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
paidAt | date-time | Paid at |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_issued | Only an issued invoice is paid. |
Events it publishes: invoices.paid
What it writes: invoice (update) — Marks it paid; invoice_event (insert) — Records the payment
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/mark_paid \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"paidAt":"2026-11-02T09:00:00Z"}'{
"ok": true,
"result": "…",
"events": [
"invoices.paid"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.markPaid("INVOICE_ID", {
paidAt: "2026-11-02T09:00:00Z"
})era invoices mark_paid INVOICE_ID \
--paid-at "2026-11-02T09:00:00Z"{
"name": "era__invoices__mark_paid",
"arguments": {
"id": "INVOICE_ID",
"paidAt": "2026-11-02T09:00:00Z"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_issued · 429 slow down
invoices.mark_sent
Mark as sent — Records that the invoice reached the person — Saleor’s "sent" event.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoices/{id}/actions/mark_sent- Canonical address:
POST /api/v1/actions/invoices.mark_sent
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_issued | Only an issued invoice is sent. |
Events it publishes: invoices.sent
What it writes: invoice (update) — Stamps when it was sent; invoice_event (insert) — Records the send
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/mark_sent \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"invoices.sent"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.markSent("INVOICE_ID")era invoices mark_sent INVOICE_ID{
"name": "era__invoices__mark_sent",
"arguments": {
"id": "INVOICE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_issued · 429 slow down
invoices.raise_notices
Raise invoice reminders — Machine-only (the daily routine): every issued invoice falling due within the reminder window, and every one past due, is told about once.
Needs invoices:write · changes data
403 machine_only).https://app.getera.io/api/v1/invoices/actions/raise_notices- Canonical address:
POST /api/v1/actions/invoices.raise_notices
Input
| Field | Type | Required | Notes |
|---|---|---|---|
asOf | date | As of |
Events it publishes: invoices.due_soon, invoices.overdue
What it writes: invoice (update) — Records that the reminder was raised (due soon / overdue)
Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down
invoices.send
Send by email — Emails the invoice through the notifications module; the record shows whether it went.
Needs invoices:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/invoice_events/actions/send- Canonical address:
POST /api/v1/actions/invoices.send
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Invoice · id of invoice |
to | string | Email · max 320 | |
message | string | Message · max 4000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_issued | Only an issued invoice is sent. |
invoice_recipient_missing | There must be an address to send to. |
Events it publishes: invoices.send_requested
What it writes: invoice_event (insert) — Records who it was sent to
curl -X POST https://app.getera.io/api/v1/invoice_events/actions/send \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"INVOICE_ID"}'{
"ok": true,
"result": "…",
"events": [
"invoices.send_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoiceEvents.send({
id: "INVOICE_ID"
})era invoice_events send \
--id "INVOICE_ID"{
"name": "era__invoices__send",
"arguments": {
"id": "INVOICE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_issued, invoice_recipient_missing · 429 slow down
invoices.settle_from_agreement
Settle from the agreement — Machine-only: marks the agreement’s issued invoice paid once finance has collected it in full.
Needs invoices:write · changes data
403 machine_only).https://app.getera.io/api/v1/invoice_events/actions/settle_from_agreement- Canonical address:
POST /api/v1/actions/invoices.settle_from_agreement
Input
| Field | Type | Required | Notes |
|---|---|---|---|
agreementId | uuid | yes | Agreement · id of agreement |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_issued | The agreement must have an issued invoice. |
invoice_not_fully_collected | The agreement must be collected in full. |
Events it publishes: invoices.paid
What it writes: invoice (update) — Marks it paid; invoice_event (insert) — Records the settlement
Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_issued, invoice_not_fully_collected · 429 slow down
invoices.update_draft
Edit a draft invoice — Changes the lines, the due date or the notes — only while it is a draft.
Needs invoices:write · changes data
https://app.getera.io/api/v1/invoices/{id}/actions/update_draft- Canonical address:
POST /api/v1/actions/invoices.update_draft
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
dueDate | date | Due | |
notes | string | Notes · max 4000 | |
taxCountry | string | Tax country · max 2 | |
lines | object / array | Lines |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_not_draft | Only a draft is edited. |
invoice_needs_lines | An invoice needs at least one line. |
invalid_invoice_lines | Every line needs a description, a positive quantity and a price. |
Events it publishes: invoices.updated
What it writes: invoice (update) — Updates the header and totals; invoice_line (upsert) — Rewrites the lines; invoice_event (insert) — Records the edit
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/update_draft \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"dueDate":"2026-11-02"}'{
"ok": true,
"result": "…",
"events": [
"invoices.updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.updateDraft("INVOICE_ID", {
dueDate: "2026-11-02"
})era invoices update_draft INVOICE_ID \
--due-date "2026-11-02"{
"name": "era__invoices__update_draft",
"arguments": {
"id": "INVOICE_ID",
"dueDate": "2026-11-02"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_not_draft, invoice_needs_lines, invalid_invoice_lines · 429 slow down
invoices.void
Void an invoice — Withdraws a draft or an unpaid issued invoice. Its number stays used.
Needs invoices:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/invoices/{id}/actions/void- Canonical address:
POST /api/v1/actions/invoices.void
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Invoice · id of invoice |
reason | string | yes | Reason · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invoice_not_found | The invoice must exist. |
invoice_already_void | An invoice is voided once. |
invoice_paid_needs_credit_note | A paid invoice is credited, never voided. |
Events it publishes: invoices.voided
What it writes: invoice (update) — Marks it void; invoice_event (insert) — Records why
curl -X POST https://app.getera.io/api/v1/invoices/INVOICE_ID/actions/void \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"reason":"Reason"}'{
"ok": true,
"result": "…",
"events": [
"invoices.voided"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.invoices.void("INVOICE_ID", {
reason: "Reason"
})era invoices void INVOICE_ID \
--reason "Reason"{
"name": "era__invoices__void",
"arguments": {
"id": "INVOICE_ID",
"reason": "Reason"
}
}Errors: 401 no or expired credential · 403 the credential does not hold invoices:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invoice_not_found, invoice_already_void, invoice_paid_needs_credit_note · 429 slow down
Data models
InvoiceAttachment
Table invoice_attachment · Resource invoice_attachments
GET /api/v1/invoice_attachmentsGET /api/v1/invoice_attachments/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
invoice_id | string (uuid) · uuid | invoice | ||
file_id | string (uuid) · uuid | file | ||
added_by_user_id | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: invoice_id, file_id
InvoiceEvent
Table invoice_event · Resource invoice_events
GET /api/v1/invoice_eventsGET /api/v1/invoice_events/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
invoice_id | string (uuid) · uuid | invoice | ||
type | string · text | created, updated, issued, sent, paid, voided, credited, note_added, attachment_added, attachment_removed, send_requested | ||
message | string · text | yes | ||
parameters | object · jsonb | |||
actor_user_id | string (uuid) · uuid | yes | app_user | |
occurred_at | string (date-time) · timestamptz |
Primary key: id
InvoiceLine
Table invoice_line · Resource invoice_lines
GET /api/v1/invoice_linesGET /api/v1/invoice_lines/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
invoice_id | string (uuid) · uuid | invoice | ||
description | string · text | |||
quantity | string (decimal) · numeric | |||
item_module | string · text | yes | ||
item_record_id | string (uuid) · uuid | yes | ||
currency | string · bpchar | max 3 | ||
unit_amount | string (int64) · int8 | |||
discount_amount | string (int64) · int8 | |||
tax_class_id | string (uuid) · uuid | yes | tax_class | |
tax_class_name | string · text | yes | ||
tax_rate | string (decimal) · numeric | |||
prices_entered_with_tax | boolean · bool | |||
net_amount | string (int64) · int8 | |||
tax_amount | string (int64) · int8 | |||
total_amount | string (int64) · int8 | |||
display_order | integer · int4 | |||
created_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((currency ~ '^[A-Z]{3}$'::text))CHECK ((discount_amount >= 0))CHECK ((net_amount >= 0))CHECK ((quantity > (0)::numeric))CHECK ((tax_amount >= 0))CHECK (((tax_rate >= (0)::numeric) AND (tax_rate <= (100)::numeric)))CHECK ((total_amount >= 0))CHECK ((unit_amount >= 0))
Invoice
Table invoice · Resource invoices
GET /api/v1/invoicesGET /api/v1/invoices/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
kind | string · text | invoice, credit_note | ||
status | string · text | draft, issued, paid, void | ||
number | string · text | yes | ||
customer_id | string (uuid) · uuid | customer | ||
agreement_id | string (uuid) · uuid | yes | agreement | |
credited_invoice_id | string (uuid) · uuid | yes | invoice | |
source_module | string · text | yes | ||
source_record_id | string (uuid) · uuid | yes | ||
currency | string · bpchar | max 3 | ||
subtotal_amount | string (int64) · int8 | |||
discount_total_amount | string (int64) · int8 | |||
tax_total_amount | string (int64) · int8 | |||
total_amount | string (int64) · int8 | |||
billing_name | string · text | yes | ||
billing_email | string · text | yes | ||
billing_address | string · text | yes | ||
tax_country | string · bpchar | yes | max 2 | |
issue_date | string (date) · date | yes | ||
due_date | string (date) · date | yes | ||
issued_at | string (date-time) · timestamptz | yes | ||
sent_at | string (date-time) · timestamptz | yes | ||
paid_at | string (date-time) · timestamptz | yes | ||
voided_at | string (date-time) · timestamptz | yes | ||
void_reason | string · text | yes | ||
notes | string · text | yes | ||
external_url | string · text | yes | ||
created_by_user_id | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
due_notice_at | string (date-time) · timestamptz | yes | ||
overdue_notice_at | string (date-time) · timestamptz | yes |
Primary key: id
Checks:
CHECK (((kind <> 'credit_note'::text) OR (credited_invoice_id IS NOT NULL)))CHECK ((currency ~ '^[A-Z]{3}$'::text))CHECK ((discount_total_amount >= 0))CHECK (((status = ANY (ARRAY['draft'::text, 'void'::text])) OR (number IS NOT NULL)))CHECK ((subtotal_amount >= 0))CHECK (((tax_country IS NULL) OR (tax_country ~ '^[A-Z]{2}$'::text)))CHECK ((tax_total_amount >= 0))CHECK ((total_amount >= 0))CHECK (((status <> 'void'::text) OR (void_reason IS NOT NULL)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
invoices.attachments_changed | An invoice’s files changed. | id |
invoices.credit_note_issued | A credit note was issued. | id, number, creditedInvoiceId |
invoices.draft_created | A draft invoice was started. | id, customerId, agreementId |
invoices.due_soon | An issued invoice falls due soon. | id, number, customerId, customerName, email, totalAmount, currency, totalText, dueDate, ownerUserId, daysUntilDue |
invoices.issued | An invoice was numbered and issued. | id, number, customerId, agreementId, totalAmount, currency |
invoices.note_added | A note was added to an invoice. | id |
invoices.overdue | An issued invoice passed its due date unpaid. | id, number, customerId, customerName, email, totalAmount, currency, totalText, dueDate, ownerUserId, daysOverdue |
invoices.paid | An invoice was settled. | id, agreementId |
invoices.send_requested | An invoice was sent by email; notifications delivers it. | id, eventId, to, subject, text |
invoices.sent | An invoice was sent. | id |
invoices.updated | A draft invoice was edited. | id |
invoices.voided | An invoice was voided. | id, agreementId |