v5.78.0 18 June 2026

Create and issue a credit note

What changed

Two new endpoints complete the credit-note lifecycle over the API.

POST /v1/credit-notes creates a credit note. With status omitted or set to draft, it records the note and its lines with no ledger effect. With status: "issued", it also posts the issue journal in the same request, so you can create and issue in a single call.

curl -X POST https://api.speybooks.com/v1/credit-notes \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
        "creditNoteType": "sales",
        "contactId": "cont_42",
        "issueDate": "2026-06-18",
        "reason": "Goods returned",
        "lines": [
          { "description": "Returned widget", "quantity": "1",
            "unitPrice": "100.00", "vatRate": 20, "accountId": "acc_4000" }
        ]
      }'
{
  "data": {
    "creditNoteId": "cn_318",
    "creditNoteNumber": "CN-0001",
    "status": "draft"
  }
}

POST /v1/credit-notes/{id}/issue issues a draft, moving it to issued and posting the journal.

curl -X POST https://api.speybooks.com/v1/credit-notes/cn_318/issue \
  -H "Authorization: Bearer $TOKEN"
{
  "data": { "creditNoteId": "cn_318", "status": "issued" }
}

Why it matters

Until now you could read, allocate, de-allocate and void credit notes over the API, but the entry point was missing: a note had to exist before any of that applied. These two endpoints close that gap, so the whole credit-note lifecycle is now yours to drive programmatically, from creation through to settlement or void. Create a draft and review it before it touches your VAT position, or create and issue in one call when you already know the figures. Either way the document number is drawn only once the note is committed, so a rejected request never burns a number and your credit-note sequence stays gap-free for your records and your auditor.

Proof and impact

A create-as-issued and a standalone issue both post the issue journal inside the same database transaction as the state change, so a note cannot be issued without its journal or carry a journal without being issued. That equivalence is verified in both directions on every deploy. Issuing requires every line to carry an account; a create-as-issued with an unmapped line is rejected before the document number is drawn (enforced in the credit-note schema, migration 107).

Both endpoints are idempotent under an Idempotency-Key: a replay returns the original note rather than creating a second. Both require a signed-in user; an API key is refused with 403, the same posture as void and de-allocation, so every credit-note creation and issue carries a named human actor on its audit record.

Money on the wire is exact: line quantity and unitPrice are decimal strings and vatRate is an integer percentage, never a floating-point number.

Breaking changes: none. These are additive endpoints.

Known issues

None.