ReferenceReference by module

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

ResourceTableList and readPOSTPATCHDELETE
invoice_attachmentsinvoice_attachment/api/v1/invoice_attachments
invoice_eventsinvoice_event/api/v1/invoice_events
invoice_linesinvoice_line/api/v1/invoice_lines
invoicesinvoice/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

ActionNeedsWhat it does
invoices.add_noteinvoices:writeAdd a note
invoices.attach_fileinvoices:writeAttach a file
invoices.create_draftinvoices:writeStart an invoice
invoices.create_from_agreementinvoices:writeInvoice an agreement
invoices.detach_fileinvoices:writeDetach a file
invoices.issueinvoices:writeIssue an invoice
invoices.issue_credit_noteinvoices:manageIssue a credit note
invoices.mark_paidinvoices:writeMark as paid
invoices.mark_sentinvoices:writeMark as sent
invoices.raise_noticesinvoices:writeRaise invoice reminders
invoices.sendinvoices:writeSend by email
invoices.settle_from_agreementinvoices:writeSettle from the agreement
invoices.update_draftinvoices:writeEdit a draft invoice
invoices.voidinvoices:manageVoid 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

POSThttps://app.getera.io/api/v1/invoice_events/actions/add_note
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.add_note

Input

FieldTypeRequiredNotes
iduuidyesInvoice · id of invoice
messagestringyesNote · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoice_attachments/actions/attach_file
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.attach_file

Input

FieldTypeRequiredNotes
iduuidyesInvoice · id of invoice
fileIduuidyesFile · id of file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
attachment_file_not_foundThe 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/actions/create_draft
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.create_draft

Input

FieldTypeRequiredNotes
customerIduuidyesBilled to · id of customer
agreementIduuidAgreement · id of agreement
currencystringCurrency · max 3
taxCountrystringTax country · max 2
dueDatedateDue
notesstringNotes · max 4000
linesobject / arrayyesLines

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
customer_not_foundThe person must exist.
customer_archivedAn archived person is not invoiced.
invalid_currencyThe currency must be an ISO-4217 code.
agreement_not_foundA named agreement must exist.
invoice_needs_linesAn invoice needs at least one line.
invalid_invoice_linesEvery 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":{}}'
Response
{
  "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":{}}'
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/actions/create_from_agreement
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.create_from_agreement

Input

FieldTypeRequiredNotes
agreementIduuidyesAgreement · id of agreement
taxCountrystringTax country · max 2

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
agreement_not_invoiceableA cancelled agreement is not invoiced.
agreement_already_invoicedAn 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoice_events/actions/detach_file
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.detach_file

Input

FieldTypeRequiredNotes
iduuidyesInvoice · id of invoice
fileIduuidyesFile · id of file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
attachment_not_foundThe 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/issue
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.issue

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice
issueDatedateIssue date
dueDatedateDue

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_draftOnly a draft is issued.
invoice_needs_linesAn 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/issue_credit_note
API key or OAuth token · invoices:manage
  • Canonical address: POST /api/v1/actions/invoices.issue_credit_note

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_creditableOnly an issued or paid invoice is credited.
invoice_already_creditedAn 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/mark_paid
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.mark_paid

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice
paidAtdate-timePaid at

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_issuedOnly 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/mark_sent
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.mark_sent

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_issuedOnly 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 '{}'
Response
{
  "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
tools/call
{
  "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

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/invoices/actions/raise_notices
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.raise_notices

Input

FieldTypeRequiredNotes
asOfdateAs 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

POSThttps://app.getera.io/api/v1/invoice_events/actions/send
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.send

Input

FieldTypeRequiredNotes
iduuidyesInvoice · id of invoice
tostringEmail · max 320
messagestringMessage · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_issuedOnly an issued invoice is sent.
invoice_recipient_missingThere 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"}'
Response
{
  "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"
tools/call
{
  "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

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/invoice_events/actions/settle_from_agreement
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.settle_from_agreement

Input

FieldTypeRequiredNotes
agreementIduuidyesAgreement · id of agreement

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_issuedThe agreement must have an issued invoice.
invoice_not_fully_collectedThe 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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/update_draft
API key or OAuth token · invoices:write
  • Canonical address: POST /api/v1/actions/invoices.update_draft

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice
dueDatedateDue
notesstringNotes · max 4000
taxCountrystringTax country · max 2
linesobject / arrayLines

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_not_draftOnly a draft is edited.
invoice_needs_linesAn invoice needs at least one line.
invalid_invoice_linesEvery 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"}'
Response
{
  "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"
tools/call
{
  "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

POSThttps://app.getera.io/api/v1/invoices/{id}/actions/void
API key or OAuth token · invoices:manage
  • Canonical address: POST /api/v1/actions/invoices.void

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Invoice · id of invoice
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invoice_not_foundThe invoice must exist.
invoice_already_voidAn invoice is voided once.
invoice_paid_needs_credit_noteA 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"}'
Response
{
  "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"
tools/call
{
  "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_attachments
  • GET /api/v1/invoice_attachments/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
invoice_idstring (uuid) · uuidinvoice
file_idstring (uuid) · uuidfile
added_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Unique: invoice_id, file_id

InvoiceEvent

Table invoice_event · Resource invoice_events

  • GET /api/v1/invoice_events
  • GET /api/v1/invoice_events/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
invoice_idstring (uuid) · uuidinvoice
typestring · textcreated, updated, issued, sent, paid, voided, credited, note_added, attachment_added, attachment_removed, send_requested
messagestring · textyes
parametersobject · jsonb
actor_user_idstring (uuid) · uuidyesapp_user
occurred_atstring (date-time) · timestamptz

Primary key: id

InvoiceLine

Table invoice_line · Resource invoice_lines

  • GET /api/v1/invoice_lines
  • GET /api/v1/invoice_lines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
invoice_idstring (uuid) · uuidinvoice
descriptionstring · text
quantitystring (decimal) · numeric
item_modulestring · textyes
item_record_idstring (uuid) · uuidyes
currencystring · bpcharmax 3
unit_amountstring (int64) · int8
discount_amountstring (int64) · int8
tax_class_idstring (uuid) · uuidyestax_class
tax_class_namestring · textyes
tax_ratestring (decimal) · numeric
prices_entered_with_taxboolean · bool
net_amountstring (int64) · int8
tax_amountstring (int64) · int8
total_amountstring (int64) · int8
display_orderinteger · int4
created_atstring (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/invoices
  • GET /api/v1/invoices/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
kindstring · textinvoice, credit_note
statusstring · textdraft, issued, paid, void
numberstring · textyes
customer_idstring (uuid) · uuidcustomer
agreement_idstring (uuid) · uuidyesagreement
credited_invoice_idstring (uuid) · uuidyesinvoice
source_modulestring · textyes
source_record_idstring (uuid) · uuidyes
currencystring · bpcharmax 3
subtotal_amountstring (int64) · int8
discount_total_amountstring (int64) · int8
tax_total_amountstring (int64) · int8
total_amountstring (int64) · int8
billing_namestring · textyes
billing_emailstring · textyes
billing_addressstring · textyes
tax_countrystring · bpcharyesmax 2
issue_datestring (date) · dateyes
due_datestring (date) · dateyes
issued_atstring (date-time) · timestamptzyes
sent_atstring (date-time) · timestamptzyes
paid_atstring (date-time) · timestamptzyes
voided_atstring (date-time) · timestamptzyes
void_reasonstring · textyes
notesstring · textyes
external_urlstring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
due_notice_atstring (date-time) · timestamptzyes
overdue_notice_atstring (date-time) · timestamptzyes

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.

EventWhat happensPayload
invoices.attachments_changedAn invoice’s files changed.id
invoices.credit_note_issuedA credit note was issued.id, number, creditedInvoiceId
invoices.draft_createdA draft invoice was started.id, customerId, agreementId
invoices.due_soonAn issued invoice falls due soon.id, number, customerId, customerName, email, totalAmount, currency, totalText, dueDate, ownerUserId, daysUntilDue
invoices.issuedAn invoice was numbered and issued.id, number, customerId, agreementId, totalAmount, currency
invoices.note_addedA note was added to an invoice.id
invoices.overdueAn issued invoice passed its due date unpaid.id, number, customerId, customerName, email, totalAmount, currency, totalText, dueDate, ownerUserId, daysOverdue
invoices.paidAn invoice was settled.id, agreementId
invoices.send_requestedAn invoice was sent by email; notifications delivers it.id, eventId, to, subject, text
invoices.sentAn invoice was sent.id
invoices.updatedA draft invoice was edited.id
invoices.voidedAn invoice was voided.id, agreementId