/v1/meRetrieve the account
Returns the API tier of the key and this month’s document usage against the tier limit.
curl https://invoicestudio.co/api/v1/me \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"One POST creates an invoice or quote and gives you back a total, a PDF, a UBL 2.1 e-invoice and a shareable link — no template engine, no PDF toolchain, no rounding bugs of your own. 250 documents a month are free, keys are issued instantly, and every event can be pushed to a signed webhook.
curl -X POST https://invoicestudio.co/api/v1/invoices \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"version":1,"type":"invoice","number":"INV-1001","issueDate":"2026-09-01","dueDate":"2026-09-15","currency":"EUR","from":"Acme GmbH\nHauptstrasse 1\n10115 Berlin","to":"Globex Ltd\n5 King Street\nLondon","items":[{"name":"Consulting","description":"September retainer","quantity":10,"unitCostCents":12000}],"tax":{"title":"VAT","kind":"percent","value":19},"notes":"Thank you for your business.","paymentLink":"https://buy.stripe.com/test_123"}'$0 / forever
250 documents / month
Everything in the API, no card. Enough to build and ship.
$19 / month
1,000 documents / month
For a live product with steady invoicing volume.
$49 / month
5,000 documents / month
For platforms invoicing on behalf of many customers.
A document is one invoice or quote created via POST /v1/invoices. PDF, UBL, reads and webhooks are unmetered. Hard cap — no overage bills. Need more than 5,000? Contact us.
Create one in the developer dashboard. It is shown once — store it as INVOICESTUDIO_API_KEY.
The response carries the computed total and the PDF and UBL URLs.
curl -X POST https://invoicestudio.co/api/v1/invoices \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"version":1,"type":"invoice","number":"INV-1001","issueDate":"2026-09-01","dueDate":"2026-09-15","currency":"EUR","from":"Acme GmbH\nHauptstrasse 1\n10115 Berlin","to":"Globex Ltd\n5 King Street\nLondon","items":[{"name":"Consulting","description":"September retainer","quantity":10,"unitCostCents":12000}],"tax":{"title":"VAT","kind":"percent","value":19},"notes":"Thank you for your business.","paymentLink":"https://buy.stripe.com/test_123"}'Rendered on demand, no extra quota.
curl https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/pdf \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-o invoice.pdfPublishes a public link and returns the invoice with share_url set.
curl -X POST https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/share \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"Authorization: Bearer is_live_… on every request. There are no cookies and no OAuth.x-request-id. Quote it when you contact support.Every non-2xx response uses the same envelope. error.code maps 1:1 to the HTTP status, so branch on the code, not the message.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "items[0].quantity: must be a number between 0 and 1000000.",
"param": "items[0].quantity",
"request_id": "req_9tQ0Zg2mCq3xVb7A"
}
}| code | HTTP | type | Meaning |
|---|---|---|---|
| unauthorized | 401 | authentication_error | Missing, malformed or unknown API key. |
| forbidden | 403 | permission_error | The key is valid but not allowed to do this. |
| not_found | 404 | invalid_request_error | No such resource for this account. |
| invalid_request | 400 | invalid_request_error | The body or a query parameter failed validation; `error.param` names the field. |
| rate_limited | 429 | rate_limit_error | Too many requests for this key, or too many failed authentications from this IP. |
| quota_exceeded | 402 | quota_error | The monthly document quota for your tier is used up. |
| idempotency_conflict | 409 | idempotency_error | The `Idempotency-Key` was reused with a different body, or a matching request is in flight. |
| internal | 500 | api_error | Something failed on our side. Retry, and quote the `request_id`. |
Idempotency-Key header (any unique string, ≤ 255 characters) on POST /v1/invoices so a retried request cannot create a second invoice.201 response and adds idempotent-replayed: true.409 idempotency_conflict.GET /v1/invoices returns newest first, limit items per page (1–100, default 20).has_more is true, pass the response’s next_cursor as starting_after to fetch the next page. On the last page next_cursor is null.x-ratelimit-limit and x-ratelimit-remaining.429 rate_limited with a Retry-After header in seconds. Back off; do not hot-retry./v1/meReturns the API tier of the key and this month’s document usage against the tier limit.
curl https://invoicestudio.co/api/v1/me \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/invoicesReturns your invoices and quotes, newest first. Page forward by passing the previous response’s `next_cursor` as `starting_after`.
curl "https://invoicestudio.co/api/v1/invoices?limit=20" \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/invoicesCreates an invoice or quote and counts one document against the monthly quota. Send an `Idempotency-Key` to make retries safe: the stored response is replayed with `idempotent-replayed: true`, while the same key with a different body — or a request still in flight — returns 409.
curl -X POST https://invoicestudio.co/api/v1/invoices \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"version":1,"type":"invoice","number":"INV-1001","issueDate":"2026-09-01","dueDate":"2026-09-15","currency":"EUR","from":"Acme GmbH\nHauptstrasse 1\n10115 Berlin","to":"Globex Ltd\n5 King Street\nLondon","items":[{"name":"Consulting","description":"September retainer","quantity":10,"unitCostCents":12000}],"tax":{"title":"VAT","kind":"percent","value":19},"notes":"Thank you for your business.","paymentLink":"https://buy.stripe.com/test_123"}'/v1/invoices/{id}Fetches one invoice or quote by id, including its status and share link.
curl https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55 \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/invoices/{id}Permanently deletes an invoice and any share link. Emits `invoice.deleted`. Deleting does not refund the document against the monthly quota.
curl -X DELETE https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55 \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/invoices/{id}/pdfRenders the invoice as a PDF. The response is a file attachment named after the document.
curl https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/pdf \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-o invoice.pdf/v1/invoices/{id}/ublReturns the invoice as UBL 2.1 XML for e-invoicing pipelines.
curl https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/ubl \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-o invoice.xml/v1/invoices/{id}/sharePublishes a public link for the invoice and returns the invoice with `share_url` set. Idempotent: repeated calls return the same link. Emits `invoice.shared`.
curl -X POST https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/share \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/webhooksReturns every endpoint on the account, oldest first. Secrets are never included.
curl https://invoicestudio.co/api/v1/webhooks \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/webhooksRegisters a public https URL for the given events and returns the signing secret — the only time it is shown. At most 10 endpoints per account.
curl -X POST https://invoicestudio.co/api/v1/webhooks \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks/invoicestudio","events":["invoice.created","invoice.shared"]}'/v1/webhooks/{id}Fetches one endpoint by id. The signing secret is not returned.
curl https://invoicestudio.co/api/v1/webhooks/3a7c1f60-9d21-4a4e-9f70-1b2c3d4e5f60 \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"/v1/webhooks/{id}Removes the endpoint. Pending deliveries for it stop.
curl -X DELETE https://invoicestudio.co/api/v1/webhooks/3a7c1f60-9d21-4a4e-9f70-1b2c3d4e5f60 \
-H "Authorization: Bearer $INVOICESTUDIO_API_KEY"Register an https endpoint (dashboard or POST /v1/webhooks) and we POST an event to it. Deliveries are not authenticated with your API key — verify the signature instead.
invoice.created — Sent when an invoice or quote is created through the API. `data.object` is the Invoice.invoice.shared — Sent when a share link is created for an invoice. `data.object` is the Invoice, with `share_url` set.invoice.viewed — Sent the first time a recipient opens the shared invoice page. `data.object` is the Invoice.invoice.deleted — Sent when an invoice is deleted. `data.object` is the Deleted stub.{
"id": "evt_9tQ0Zg2mCq3xVb7A",
"object": "event",
"type": "invoice.created",
"created": 1772620200,
"data": {
"object": {
"id": "6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55",
"object": "invoice",
"type": "invoice",
"number": "INV-1001",
"currency": "EUR",
"total_cents": 142800,
"balance_due_cents": 142800,
"status": "draft",
"created_at": "2026-09-01T09:30:00.000Z",
"pdf_url": "https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/pdf",
"ubl_url": "https://invoicestudio.co/api/v1/invoices/6f1c9b6e-0f2a-4a3a-9a12-2f4d8c9b1a55/ubl",
"share_url": null,
"data": {
"version": 1,
"type": "invoice",
"number": "INV-1001",
"issueDate": "2026-09-01",
"dueDate": "2026-09-15",
"currency": "EUR",
"from": "Acme GmbH\nHauptstrasse 1\n10115 Berlin",
"to": "Globex Ltd\n5 King Street\nLondon",
"items": [
{
"name": "Consulting",
"description": "September retainer",
"quantity": 10,
"unitCostCents": 12000
}
],
"tax": {
"title": "VAT",
"kind": "percent",
"value": 19
},
"notes": "Thank you for your business.",
"paymentLink": "https://buy.stripe.com/test_123"
}
}
}
}Each delivery carries InvoiceStudio-Signature: t=<unix-seconds>,v1=<hex>, where v1 is HMAC-SHA256 of the string "<t>.<raw request body>", keyed with the endpoint secret (whsec_…), hex-encoded. Reject anything older than 300 seconds and compare in constant time.
import crypto from 'node:crypto'
// Verify against the RAW body, before JSON.parse.
export function verify(secret, header, rawBody, nowS = Date.now() / 1000) {
try {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const t = Number(parts.t)
if (!Number.isFinite(t) || Math.abs(nowS - t) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(parts.v1 ?? '', 'hex')
return a.length === b.length && crypto.timingSafeEqual(a, b)
} catch {
return false
}
}
// req.headers['invoicestudio-signature'] carries t=…,v1=…import hashlib, hmac, time
# header = request.headers["InvoiceStudio-Signature"]
def verify(secret: str, header: str, raw_body: bytes) -> bool:
try:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
v1 = parts["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - t) > 300:
return False
expected = hmac.new(
secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, v1)data: { object: { id, object }, truncated: true } — re-fetch the resource over the API when you see truncated.GET /v1/invoices/{id}/ubl returns the document as a base UBL 2.1 Invoice XML: supplier and customer parties, the issue date, currency, one InvoiceLine per item with quantity and unit price, a TaxTotal/TaxSubtotal when there is tax, the due date when set, the legal monetary totals, and payment means (your bank details or payment link) when you provide them.
Honest limitation: this is valid UBL 2.1, but it is notEN 16931 / PEPPOL validated. Addresses are free text, so the structured postal code and country fields those profiles require are not populated, and there are no VAT identifiers or scheme ids. Use it to feed your own e-invoicing pipeline — do not expect it to pass a PEPPOL access point unmodified.
Thin, dependency-free clients over the same REST API — typed responses, the error envelope raised as an exception, and idempotency keys handled for you.
npm i @claudiumarius/invoicestudio-sdkimport { InvoiceStudio } from '@claudiumarius/invoicestudio-sdk'
const client = new InvoiceStudio(process.env.INVOICESTUDIO_API_KEY!)
const inv = await client.invoices.create({
version: 1,
type: 'invoice',
number: 'INV-1001',
issueDate: '2026-09-01',
currency: 'EUR',
from: 'Acme GmbH',
to: 'Globex Ltd',
items: [{ name: 'Consulting', quantity: 10, unitCostCents: 12000 }],
})
console.log(inv.id, inv.total_cents, inv.pdf_url)pip install invoicestudioimport os
from invoicestudio import InvoiceStudio
client = InvoiceStudio(os.environ["INVOICESTUDIO_API_KEY"])
inv = client.invoices.create({
"version": 1,
"type": "invoice",
"number": "INV-1001",
"issueDate": "2026-09-01",
"currency": "EUR",
"from": "Acme GmbH",
"to": "Globex Ltd",
"items": [{"name": "Consulting", "quantity": 10, "unitCostCents": 12000}],
})
print(inv["id"], inv["total_cents"], inv["pdf_url"])