Developers
The payed.me API
Everything you can do to a document or a record in the portal, your own software can do over HTTP. Create invoices from your own system, keep a client list in step with your CRM, push tracked hours in at the end of the week.
Base URL
https://api.payed.me/apiAvailability
API tokens are included from the Professional plan. Every endpoint also stays behind whatever plan already gated it in the portal — a token never reaches further than your own account does.
Authenticating
Create a token in the portal underSettings → API tokens. It is shown once, at the moment you create it, and never again — we store only a hash of it, so nobody can recover it for you afterwards. Keep it where you keep your other secrets, and if you lose it, revoke it and create another.
Send it on every request as a bearer token:
curl https://api.payed.me/api/invoices \
-H "Authorization: Bearer pm_live_your_token_here"A token is accepted in that header and nowhere else — not in a cookie, not in a query string. It identifies your account, so requests made with it see exactly the invoices, clients and records you see when you sign in.
What a token cannot do
A token reaches documents and records. It does not reach anything that would let it entrench itself or change what your account is:
- It cannot create another token, or revoke one. If a token leaks, revoking it in the portal ends it — there is no way for the holder to mint a replacement.
- It cannot sign in, change your password, or touch your personal details.
- It cannot invite or remove colleagues. Team management is a portal action.
- It cannot change your subscription, start a checkout or cancel a plan.
- It cannot edit your settings or your invoice templates.
Anything not listed on this page answers 403. That is the default, not an exception: an endpoint has to be published deliberately before a token can reach it.
Things that are true of every endpoint
Read this once and the reference below reads itself.
Every list endpoint returns a two-element array
Not an object. A list response is an array whose first element is the array of rows and whose second element is thetotal number of rows matching your filters, ignoring limitand offset. That total is what you page against.
GET /invoices?limit=2
[[{"id": "b81d4e77-…", "number": "INV-2026-014"}, {"id": "0c92f13a-…", "number": "INV-2026-013"}], 42]Two rows came back; the total says 42 match. It is the single easiest thing to get wrong about this API, so it is the first thing on this page.
Paging
On the list endpoints, limitand offset are passed straight through to the database withno default and no maximum. Omitting limit returns every matching row, however many that is — always send one.
On GET /invoices/dashboard/customer-revenue the same parameter means something else: it is clamped to between 1 and 8, and defaults to 5. It is the number of clients in the breakdown, not a page size.
Filtering
- search is a case-insensitive substring match, applied across a set of columns chosen per resource — an invoice search looks at its number, reference, status, type, currency, total and the client's name.
- dateFrom anddateTo accept eitherYYYY-MM-DD or a full ISO timestamp, and bound that resource's own principal date column.dateTo is snapped to the end of that day, so a range ending today includes today.That column is createdAton every resource except two: expenses range overspentAt and mileage overtravelledOn — the date the thing happened, not the date it was entered. Note that invoices range over createdAt, not their issue or due date.
- status takes both forms: repeat it (?status=draft&status=sent) or comma-separate it (?status=draft,sent). The accepted values are that resource's own statuses — the create body's status field in the reference below lists them where a resource has them.On three resources it does not filter a status at all, because they have none: on expenses and inventory it filterscategory, and on mileage it filters vehicleType. The parameter is still spelledstatus on all three.A value that is not one of them is dropped, not rejected, so a filter made entirely of unrecognised values behaves as no filter at all and you get everything back.
- userId appears in the reference on a few list endpoints, butit does nothing for you. It is an administrator parameter for reading across accounts, and a token is never an administrator, so it is ignored rather than refused. Your requests already see only your own records — that is what the token scopes them to.
Ids, money and dates
- Every record id is a UUID. Ids you send in a body are validated as one; the reference below marks those fieldsuuid.
- Amounts are ordinary decimal numbers in the major unit —1265.50, not126550 — to at most two decimal places. currencyis a three-letter code from a fixed set, listed on the create bodies below. A line'svatRate is a percentage, so 20 means 20%.
- On an invoice you do not send totals.totalAmount andtotalVat are computed from the lines on every create and update, and overwrite whatever you sent.
- Dates are ISO — YYYY-MM-DDor a full timestamp.
A field we do not recognise is dropped, not refused
Request bodies are validated against the field list in the reference below, and any property that is not on it is stripped before the handler sees it. That is quiet by design, and it is the one failure mode to watch for: a mistyped field name does not produce an error, it produces a record missing a value. If something you sent did not stick, check its spelling against the reference first.
Client to invoice to inbox, in three calls
Paste these in order with a real token. Every field name below is checked against the reference at build time.
Create the client
Everything except `paymentRemindersEnabled` is required. The response carries the client `id` you need for the next call.
curl -X POST https://api.payed.me/api/customers \
-H "Authorization: Bearer pm_live_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Ridgeway Joinery Ltd",
"email": "accounts@ridgeway-joinery.example",
"phoneNumber": "+44 20 7946 0812",
"website": "https://ridgeway-joinery.example",
"accountNumber": "RJL-0041",
"companyRegNumber": "09876543",
"companyTaxNumber": "GB123456789",
"paymentRemindersEnabled": true,
"primaryPerson": {
"firstname": "Dana",
"lastname": "Okafor",
"email": "dana@ridgeway-joinery.example"
},
"billingAddress": {
"line": "14 Fenwick Road",
"city": "Leeds",
"state": "West Yorkshire",
"postcode": "LS8 2AB",
"country": "GB"
},
"deliveryAddress": {
"line": "14 Fenwick Road",
"city": "Leeds",
"state": "West Yorkshire",
"postcode": "LS8 2AB",
"country": "GB"
}
}'Response (abridged)
{
"id": "3f6a1c58-9b2d-4f0e-a7c1-8d5e2b41f930",
"name": "Ridgeway Joinery Ltd",
"email": "accounts@ridgeway-joinery.example",
...
}Invoice them
Use the client `id` from step one as `customerId`. You do not send totals — `totalAmount` and `totalVat` are computed from the lines and overwrite anything you send. `vatRate` is a percentage, so 20 means 20%.
curl -X POST https://api.payed.me/api/invoices \
-H "Authorization: Bearer pm_live_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"customerId": "3f6a1c58-9b2d-4f0e-a7c1-8d5e2b41f930",
"number": "INV-2026-014",
"reference": "Kitchen fit-out, phase 1",
"currency": "GBP",
"dueDate": "2026-10-09",
"items": [
{
"name": "Cabinetry install",
"description": "3 days on site",
"qty": 3,
"price": 480,
"vatRate": 20
},
{
"name": "Materials",
"qty": 1,
"price": 1265.5,
"vatRate": 20
}
]
}'Response (abridged)
{
"id": "b81d4e77-3a05-42c9-9f6b-1c0a7e5d2384",
"number": "INV-2026-014",
"status": "draft",
"currency": "GBP",
"totalAmount": 3246.60,
"totalVat": 541.10,
...
}Email it to them
No body. The PDF is rendered and attached server-side, and the mail goes to the client’s email address. The answer is 201 with `{"ksef": null}` once the email has gone. On an account that files to KSeF (a Polish issuer with a KSeF token added in the portal’s Account settings) it is `{"ksef": "queued"}` instead: the invoice is queued for KSeF and the email goes out after KSeF accepts it.
curl -X POST https://api.payed.me/api/invoices/b81d4e77-3a05-42c9-9f6b-1c0a7e5d2384/send \
-H "Authorization: Bearer pm_live_your_token_here"Response (abridged)
{
"ksef": null
}Endpoints
Every published route, with its parameters and body fields. Generated from the API itself.
Invoices
ON EVERY PLAN
Create, send and track invoices, including credit notes — a credit note is an invoice with a negative total and is served by these same endpoints. `bankAccount` must be one of the bank accounts saved in your Account settings: its `number` is compared ignoring spaces and hyphens and case, and a `swift`, when you send one, must match that saved account’s; anything else answers 400 `BANK_ACCOUNT_NOT_SAVED`. An invoice’s unchanged stored account always re-saves, even if it was later removed from settings, and the field is cleared on credit notes, receipts and self-billed invoices. On an account that files to KSeF, sending an invoice files it and emails it after KSeF accepts it, and a non-draft create or update files it without the email. While KSeF is filing an invoice or has accepted it, only its payment status may change: any other edit, a move back to draft or deleted, and a delete answer 409 `KSEF_INVOICE_LOCKED`. The KSeF token settings and the UPO download are portal-only.
/api/invoicesList invoicesQuery
- limit string
- offset string
- customerId string
- search string
- userId string
- dateFrom string
- dateTo string
- status string
/api/invoicesCreate an invoiceBody — InvoiceAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| advanceAmount | number | optional | minimum 0; at most 2 decimal place(s) |
| advanceInvoiceIds | array of uuid | optional | at most 100 entries |
| advancePayments | array of AdvancePaymentInput | optional | at most 31 entries |
| amount | number | optional | must be positive; at most 2 decimal place(s) |
| date | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| fxRate | number | optional | at most 6 decimal place(s) |
| bankAccount | BankAccountInput | optional | |
| bankName | string | optional | max length 256 |
| description | string | optional | max length 256 |
| number | string | required | a valid IBAN (checksum), or 1-34 letters and digits; spaces and hyphens removed. For a Polish issuer, 20-30 digits must be a valid 26-digit NRB, and a PL prefix must be a valid PL IBAN (checked on a new or changed account) |
| ownBank | string | optional | one of: 1, 2, 3 |
| swift | string | optional | must match /^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/ |
| correctionReason | string | optional | max length 256 |
| correctionType | number | optional | one of: 1, 2, 3 |
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| deliveryDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| dueDate | ISO date | required | |
| exemptionBasis | string | optional | max length 256 |
| exemptionBasisKind | string | optional | one of: act, directive, other |
| fiscalReceipt | boolean | optional | |
| fxDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| fxFrom | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| fxRate | numeric string | optional | |
| fxRef | string | optional | max length 64 |
| fxSource | string | optional | one of: nbp, manual, ecb |
| fxTo | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| issueDate | ISO date | optional | |
| items | array of InvoiceItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| annex15 | boolean | optional | |
| description | string | optional | |
| discountPercent | number | optional | maximum 100; minimum 0; at most 2 decimal place(s) |
| eService | boolean | optional | |
| excise | number | optional | at most 2 decimal place(s) |
| gtu | string | optional | one of: GTU_01, GTU_02, GTU_03, GTU_04, GTU_05, GTU_06, GTU_07, GTU_08, GTU_09, GTU_10, GTU_11, GTU_12, GTU_13 |
| kind | string | optional | one of: goods, service |
| name | string | required | |
| ossRate | number | optional | maximum 100; minimum 0; at most 2 decimal place(s) |
| price | number | required | at most 2 decimal place(s) |
| procedure | string | optional | one of: IED, I_42, I_63, B_SPV, B_SPV_DOSTAWA, B_MPV_PROWIZJA |
| qty | number | required | at most 2 decimal place(s) |
| supplyDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| unit | string | optional | max length 256 |
| vatRate | number or VAT code (zw, np, oo) | required | at most 2 decimal place(s); from 0 to 100 |
| marginProcedure | string | optional | one of: travel, used, art, antiques |
| newTransport | NewTransportInput | optional | |
| art42_5 | boolean | required | |
| vehicles | array of NstVehicleInput | required | at most 100 entries |
| colour | string | optional | max length 256 |
| firstUse | string | required | must match /^\d{4}-\d{2}-\d{2}$/ |
| hours | string | optional | max length 256 |
| hullNumber | string | optional | max length 256 |
| id | string | optional | max length 256 |
| idKind | string | optional | one of: vin, body, chassis, frame |
| line | integer | required | minimum 1 |
| make | string | optional | max length 256 |
| mileage | string | optional | max length 256 |
| model | string | optional | max length 256 |
| registration | string | optional | max length 256 |
| serialNumber | string | optional | max length 256 |
| type | string | required | one of: land, water, air |
| vehicleType | string | optional | max length 256 |
| year | string | optional | max length 256 |
| number | string | required | |
| paidDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| partialPayments | array of PartialPaymentInput | optional | at most 100 entries |
| amount | number | optional | must be positive; at most 2 decimal place(s) |
| date | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| form | string | optional | one of: cash, card, voucher, cheque, credit, transfer, mobile, other |
| formDescription | string | optional | max length 256 |
| paymentDescription | string | optional | max length 256 |
| paymentForm | string | optional | one of: cash, card, voucher, cheque, credit, transfer, mobile, other |
| periodFrom | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| periodTo | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| reference | string | required | |
| relatedParty | boolean | optional | |
| selfBilling | boolean | optional | |
| skontoAmount | string | optional | max length 256 |
| skontoConditions | string | optional | max length 256 |
| sourceExpenseIds | array of uuid | optional | at most 200 entries |
| sourceInvoiceId | uuid | optional | |
| sourceInvoiceIds | array of uuid | optional | |
| sourceMileageIds | array of uuid | optional | at most 200 entries |
| sourceTaskIds | array of uuid | optional | at most 200 entries |
| splitPayment | boolean | optional | |
| status | string | optional | one of: draft, sent, paid, deleted |
| templateId | uuid | optional | |
| thirdParties | array of ThirdPartyInput | optional | at most 100 entries |
| addressLine | string | optional | max length 512 |
| city | string | optional | max length 256 |
| country | string | optional | must match /^[A-Z]{2}$/ |
| name | string | required | max length 512 |
| postcode | string | optional | max length 256 |
| role | string | required | one of: 1, 2, 3, 4, 6, 8, 10, 11, other |
| roleDescription | string | optional | max length 256 |
| share | number | optional | maximum 100; minimum 0; at most 6 decimal place(s) |
| taxNumber | string | optional | max length 50 |
| triangular | boolean | optional | |
| type | string | optional | one of: invoice, credit_invoice, receipt, advance, settlement |
/api/invoices/{invoiceId}Fetch one invoicePath invoiceId
/api/invoices/{invoiceId}Update an invoicePath invoiceId
Body — InvoiceAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| advanceAmount | number | optional | minimum 0; at most 2 decimal place(s) |
| advanceInvoiceIds | array of uuid | optional | at most 100 entries |
| advancePayments | array of AdvancePaymentInput | optional | at most 31 entries |
| amount | number | optional | must be positive; at most 2 decimal place(s) |
| date | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| fxRate | number | optional | at most 6 decimal place(s) |
| bankAccount | BankAccountInput | optional | |
| bankName | string | optional | max length 256 |
| description | string | optional | max length 256 |
| number | string | required | a valid IBAN (checksum), or 1-34 letters and digits; spaces and hyphens removed. For a Polish issuer, 20-30 digits must be a valid 26-digit NRB, and a PL prefix must be a valid PL IBAN (checked on a new or changed account) |
| ownBank | string | optional | one of: 1, 2, 3 |
| swift | string | optional | must match /^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/ |
| correctionReason | string | optional | max length 256 |
| correctionType | number | optional | one of: 1, 2, 3 |
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| deliveryDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| dueDate | ISO date | required | |
| exemptionBasis | string | optional | max length 256 |
| exemptionBasisKind | string | optional | one of: act, directive, other |
| fiscalReceipt | boolean | optional | |
| fxDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| fxFrom | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| fxRate | numeric string | optional | |
| fxRef | string | optional | max length 64 |
| fxSource | string | optional | one of: nbp, manual, ecb |
| fxTo | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| issueDate | ISO date | optional | |
| items | array of InvoiceItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| annex15 | boolean | optional | |
| description | string | optional | |
| discountPercent | number | optional | maximum 100; minimum 0; at most 2 decimal place(s) |
| eService | boolean | optional | |
| excise | number | optional | at most 2 decimal place(s) |
| gtu | string | optional | one of: GTU_01, GTU_02, GTU_03, GTU_04, GTU_05, GTU_06, GTU_07, GTU_08, GTU_09, GTU_10, GTU_11, GTU_12, GTU_13 |
| kind | string | optional | one of: goods, service |
| name | string | required | |
| ossRate | number | optional | maximum 100; minimum 0; at most 2 decimal place(s) |
| price | number | required | at most 2 decimal place(s) |
| procedure | string | optional | one of: IED, I_42, I_63, B_SPV, B_SPV_DOSTAWA, B_MPV_PROWIZJA |
| qty | number | required | at most 2 decimal place(s) |
| supplyDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| unit | string | optional | max length 256 |
| vatRate | number or VAT code (zw, np, oo) | required | at most 2 decimal place(s); from 0 to 100 |
| marginProcedure | string | optional | one of: travel, used, art, antiques |
| newTransport | NewTransportInput | optional | |
| art42_5 | boolean | required | |
| vehicles | array of NstVehicleInput | required | at most 100 entries |
| colour | string | optional | max length 256 |
| firstUse | string | required | must match /^\d{4}-\d{2}-\d{2}$/ |
| hours | string | optional | max length 256 |
| hullNumber | string | optional | max length 256 |
| id | string | optional | max length 256 |
| idKind | string | optional | one of: vin, body, chassis, frame |
| line | integer | required | minimum 1 |
| make | string | optional | max length 256 |
| mileage | string | optional | max length 256 |
| model | string | optional | max length 256 |
| registration | string | optional | max length 256 |
| serialNumber | string | optional | max length 256 |
| type | string | required | one of: land, water, air |
| vehicleType | string | optional | max length 256 |
| year | string | optional | max length 256 |
| number | string | required | |
| paidDate | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| partialPayments | array of PartialPaymentInput | optional | at most 100 entries |
| amount | number | optional | must be positive; at most 2 decimal place(s) |
| date | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| form | string | optional | one of: cash, card, voucher, cheque, credit, transfer, mobile, other |
| formDescription | string | optional | max length 256 |
| paymentDescription | string | optional | max length 256 |
| paymentForm | string | optional | one of: cash, card, voucher, cheque, credit, transfer, mobile, other |
| periodFrom | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| periodTo | ISO date | optional | must match /^\d{4}-\d{2}-\d{2}$/ |
| reference | string | required | |
| relatedParty | boolean | optional | |
| selfBilling | boolean | optional | |
| skontoAmount | string | optional | max length 256 |
| skontoConditions | string | optional | max length 256 |
| sourceExpenseIds | array of uuid | optional | at most 200 entries |
| sourceInvoiceId | uuid | optional | |
| sourceInvoiceIds | array of uuid | optional | |
| sourceMileageIds | array of uuid | optional | at most 200 entries |
| sourceTaskIds | array of uuid | optional | at most 200 entries |
| splitPayment | boolean | optional | |
| status | string | optional | one of: draft, sent, paid, deleted |
| templateId | uuid | optional | |
| thirdParties | array of ThirdPartyInput | optional | at most 100 entries |
| addressLine | string | optional | max length 512 |
| city | string | optional | max length 256 |
| country | string | optional | must match /^[A-Z]{2}$/ |
| name | string | required | max length 512 |
| postcode | string | optional | max length 256 |
| role | string | required | one of: 1, 2, 3, 4, 6, 8, 10, 11, other |
| roleDescription | string | optional | max length 256 |
| share | number | optional | maximum 100; minimum 0; at most 6 decimal place(s) |
| taxNumber | string | optional | max length 50 |
| triangular | boolean | optional | |
| type | string | optional | one of: invoice, credit_invoice, receipt, advance, settlement |
/api/invoices/{invoiceId}Delete an invoicePath invoiceId
/api/invoices/{invoiceId}/printDownload the PDFPath invoiceId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
/api/invoices/{invoiceId}/sendEmail it to the clientPath invoiceId
/api/invoices/draft-from-sourcesDraft an invoice from tracked work, expenses or mileageBody — InvoiceDraftRequest
| Field | Type | Required | Constraints |
|---|---|---|---|
| expenseIds | array of uuid | optional | at most 200 entries |
| mileageIds | array of uuid | optional | at most 200 entries |
| taskIds | array of uuid | optional | at most 200 entries |
/api/invoices/dashboard/statsTotals by status/api/invoices/dashboard/revenueRevenue over timeQuery
- currency string
/api/invoices/dashboard/unbilledWork not yet invoicedQuery
- currency string
/api/invoices/dashboard/customer-revenueRevenue by clientQuery
- currency string
- limit number
Quotes
ON EVERY PLAN
Quotes from draft to accepted, rejected or expired.
/api/quotesList quotesQuery
- limit string
- offset string
- customerId string
- search string
- userId string
- dateFrom string
- dateTo string
- status string
/api/quotesCreate a quoteBody — QuoteAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| expiryDate | ISO date | optional | |
| issueDate | ISO date | optional | |
| items | array of QuoteItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| description | string | optional | |
| name | string | required | |
| price | number | required | at most 2 decimal place(s) |
| qty | number | required | at most 2 decimal place(s) |
| vatRate | number | required | maximum 100; minimum 0; at most 2 decimal place(s) |
| notes | string | optional | |
| number | string | required | max length 255 |
| reference | string | required | max length 255 |
| status | string | optional | one of: draft, sent, accepted, rejected, expired |
| templateId | uuid | optional |
/api/quotes/{quoteId}Fetch one quotePath quoteId
/api/quotes/{quoteId}Update a quotePath quoteId
Body — QuoteAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| expiryDate | ISO date | optional | |
| issueDate | ISO date | optional | |
| items | array of QuoteItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| description | string | optional | |
| name | string | required | |
| price | number | required | at most 2 decimal place(s) |
| qty | number | required | at most 2 decimal place(s) |
| vatRate | number | required | maximum 100; minimum 0; at most 2 decimal place(s) |
| notes | string | optional | |
| number | string | required | max length 255 |
| reference | string | required | max length 255 |
| status | string | optional | one of: draft, sent, accepted, rejected, expired |
| templateId | uuid | optional |
/api/quotes/{quoteId}Delete a quotePath quoteId
/api/quotes/{quoteId}/printDownload the PDFPath quoteId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
/api/quotes/{quoteId}/sendEmail it to the clientPath quoteId
/api/quotes/dashboard/statsTotals by statusClients
ON EVERY PLAN
The people and companies you bill. Your plan caps how many you can save — the API answers HTTP 402 when you reach it, exactly as the portal does.
/api/customersList clientsQuery
- limit string
- offset string
- userId string
- search string
- dateFrom string
- dateTo string
/api/customersCreate a clientBody — CustomerCreateData
| Field | Type | Required | Constraints |
|---|---|---|---|
| accountNumber | string | required | |
| billingAddress | AddressData | required | |
| city | string | required | |
| country | string | required | |
| line | string | required | |
| postcode | string | required | |
| state | string | required | |
| companyRegNumber | string | required | |
| companyTaxNumber | string | required | a valid Polish NIP (10 digits, checksum) when taxCountry is PL |
| deliveryAddress | AddressData | required | |
| city | string | required | |
| country | string | required | |
| line | string | required | |
| postcode | string | required | |
| state | string | required | |
| required | |||
| name | string | required | |
| paymentRemindersEnabled | boolean | optional | |
| phoneNumber | string | required | |
| primaryPerson | PersonDetailsData | required | |
| required | |||
| firstname | string | required | |
| lastname | string | required | |
| taxCountry | string | optional | one of: AF, AX, AL, DZ, AD, AO, AI, AQ, AG, AN, SA, AR, AM, AW, AU, AT, AZ, BS, BH, BD, BB, BE, BZ, BJ, BM, BT, BY, BO, BQ, BA, BW, BR, BN, IO, BG, BF, BI, XC, CL, CN, HR, CW, CY, TD, ME, DK, DM, DO, DJ, EG, EC, ER, EE, ET, FK, FJ, PH, FI, FR, TF, GA, GM, GH, GI, GR, GD, GL, GE, GU, GG, GY, GF, GP, GT, GN, GQ, GW, HT, ES, HN, HK, IN, ID, IQ, IR, IE, IS, IL, JM, JP, YE, JE, JO, KY, KH, CM, CA, QA, KZ, KE, KG, KI, CO, KM, CG, CD, KP, XK, CR, CU, KW, LA, LS, LB, LR, LY, LI, LT, LV, LU, MK, MG, YT, MO, MW, MV, MY, ML, MT, MP, MA, MQ, MR, MU, MX, XL, FM, UM, MD, MC, MN, MS, MZ, MM, NA, NR, NP, NL, DE, NE, NG, NI, NU, NF, NO, NC, NZ, PS, OM, PK, PW, PA, PG, PY, PE, PN, PF, PL, GS, PT, PR, CF, CZ, KR, ZA, RE, RU, RO, RW, EH, BL, KN, LC, MF, VC, SV, WS, AS, SM, SN, RS, SC, SL, SG, SK, SI, SO, LK, PM, US, SZ, SD, SS, SR, SJ, SH, SY, CH, SE, TJ, TH, TW, TZ, TG, TK, TO, TT, TN, TR, TM, TV, UG, UA, UY, UZ, VU, WF, VA, HU, VE, GB, VN, IT, TL, CI, BV, CX, IM, SX, CK, VI, VG, HM, CC, MH, FO, SB, ST, TC, ZM, CV, ZW, AE, XI |
| website | string | required |
/api/customers/{customerId}Fetch one clientPath customerId
/api/customers/{customerId}Update a clientPath customerId
Body — CustomerCreateData
| Field | Type | Required | Constraints |
|---|---|---|---|
| accountNumber | string | required | |
| billingAddress | AddressData | required | |
| city | string | required | |
| country | string | required | |
| line | string | required | |
| postcode | string | required | |
| state | string | required | |
| companyRegNumber | string | required | |
| companyTaxNumber | string | required | a valid Polish NIP (10 digits, checksum) when taxCountry is PL |
| deliveryAddress | AddressData | required | |
| city | string | required | |
| country | string | required | |
| line | string | required | |
| postcode | string | required | |
| state | string | required | |
| required | |||
| name | string | required | |
| paymentRemindersEnabled | boolean | optional | |
| phoneNumber | string | required | |
| primaryPerson | PersonDetailsData | required | |
| required | |||
| firstname | string | required | |
| lastname | string | required | |
| taxCountry | string | optional | one of: AF, AX, AL, DZ, AD, AO, AI, AQ, AG, AN, SA, AR, AM, AW, AU, AT, AZ, BS, BH, BD, BB, BE, BZ, BJ, BM, BT, BY, BO, BQ, BA, BW, BR, BN, IO, BG, BF, BI, XC, CL, CN, HR, CW, CY, TD, ME, DK, DM, DO, DJ, EG, EC, ER, EE, ET, FK, FJ, PH, FI, FR, TF, GA, GM, GH, GI, GR, GD, GL, GE, GU, GG, GY, GF, GP, GT, GN, GQ, GW, HT, ES, HN, HK, IN, ID, IQ, IR, IE, IS, IL, JM, JP, YE, JE, JO, KY, KH, CM, CA, QA, KZ, KE, KG, KI, CO, KM, CG, CD, KP, XK, CR, CU, KW, LA, LS, LB, LR, LY, LI, LT, LV, LU, MK, MG, YT, MO, MW, MV, MY, ML, MT, MP, MA, MQ, MR, MU, MX, XL, FM, UM, MD, MC, MN, MS, MZ, MM, NA, NR, NP, NL, DE, NE, NG, NI, NU, NF, NO, NC, NZ, PS, OM, PK, PW, PA, PG, PY, PE, PN, PF, PL, GS, PT, PR, CF, CZ, KR, ZA, RE, RU, RO, RW, EH, BL, KN, LC, MF, VC, SV, WS, AS, SM, SN, RS, SC, SL, SG, SK, SI, SO, LK, PM, US, SZ, SD, SS, SR, SJ, SH, SY, CH, SE, TJ, TH, TW, TZ, TG, TK, TO, TT, TN, TR, TM, TV, UG, UA, UY, UZ, VU, WF, VA, HU, VE, GB, VN, IT, TL, CI, BV, CX, IM, SX, CK, VI, VG, HM, CC, MH, FO, SB, ST, TC, ZM, CV, ZW, AE, XI |
| website | string | required |
/api/customers/{customerId}Delete a clientPath customerId
Receipts
FROM BASIC
Receipts you issue against payments received.
/api/receiptsList receiptsQuery
- limit string
- offset string
- customerId string
- search string
- userId string
- dateFrom string
- dateTo string
- status string
/api/receiptsCreate a receiptBody — ReceiptAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| invoiceId | uuid | optional | |
| issueDate | ISO date | optional | |
| items | array of ReceiptItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| description | string | optional | |
| name | string | required | |
| price | number | required | at most 2 decimal place(s) |
| qty | number | required | at most 2 decimal place(s) |
| vatRate | number | required | maximum 100; minimum 0; at most 2 decimal place(s) |
| notes | string | optional | |
| number | string | required | |
| reference | string | required | |
| status | string | optional | one of: draft, issued, sent |
| templateId | uuid | optional |
/api/receipts/{receiptId}Fetch one receiptPath receiptId
/api/receipts/{receiptId}Update a receiptPath receiptId
Body — ReceiptAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| invoiceId | uuid | optional | |
| issueDate | ISO date | optional | |
| items | array of ReceiptItemAddition | required | |
| account | string | optional | one of: 200 - Sales, 260 - Other Revenue, 270 - Interest Income |
| description | string | optional | |
| name | string | required | |
| price | number | required | at most 2 decimal place(s) |
| qty | number | required | at most 2 decimal place(s) |
| vatRate | number | required | maximum 100; minimum 0; at most 2 decimal place(s) |
| notes | string | optional | |
| number | string | required | |
| reference | string | required | |
| status | string | optional | one of: draft, issued, sent |
| templateId | uuid | optional |
/api/receipts/{receiptId}Delete a receiptPath receiptId
/api/receipts/{receiptId}/printDownload the PDFPath receiptId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
/api/receipts/{receiptId}/sendEmail itPath receiptId
/api/receipts/dashboard/statsTotals by periodExpenses
FROM BASIC
What you spend, with an optional attachment on each record.
/api/expensesList expensesQuery
- limit string
- offset string
- search string
- userId string
- billable string
- customerId string
- dateFrom string
- dateTo string
- status string
/api/expensesCreate an expenseBody — ExpenseAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| billable | boolean | optional | |
| category | string | required | one of: travel, software, equipment, office, marketing, professional_services, other |
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | optional | must not be empty |
| description | string | optional | |
| netAmount | number | required | minimum 0 |
| notes | string | optional | |
| purchaseInvoiceId | uuid | optional | |
| reference | string | optional | max length 255 |
| spentAt | ISO date | required | |
| vatAmount | number | optional | minimum 0 |
| vatRate | number | optional | maximum 100; minimum 0 |
| vendor | string | required | max length 255 |
/api/expenses/{expenseId}Fetch one expensePath expenseId
/api/expenses/{expenseId}Update an expensePath expenseId
Body — ExpenseAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| billable | boolean | optional | |
| category | string | required | one of: travel, software, equipment, office, marketing, professional_services, other |
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | optional | must not be empty |
| description | string | optional | |
| netAmount | number | required | minimum 0 |
| notes | string | optional | |
| purchaseInvoiceId | uuid | optional | |
| reference | string | optional | max length 255 |
| spentAt | ISO date | required | |
| vatAmount | number | optional | minimum 0 |
| vatRate | number | optional | maximum 100; minimum 0 |
| vendor | string | required | max length 255 |
/api/expenses/{expenseId}Delete an expensePath expenseId
/api/expenses/{expenseId}/attachmentUpload a receipt image or PDFPath expenseId
Send the file as multipart/form-data. There is no JSON body on this request.
/api/expenses/{expenseId}/attachmentDownload the attachmentPath expenseId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
/api/expenses/{expenseId}/attachmentRemove the attachmentPath expenseId
/api/expenses/dashboard/statsTotals by period/api/expenses/dashboard/spendingSpending by categoryQuery
- currency string
Inventory
FROM PROFESSIONAL
Stock items, movements and counts.
/api/inventoryList itemsQuery
- limit string
- offset string
- search string
- lowStock string
- dateFrom string
- dateTo string
- status string
/api/inventoryCreate an itemBody — InventoryItemAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| category | string | required | one of: product, material, part, consumable, packaging, equipment, other |
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| description | string | optional | max length 2000 |
| location | string | optional | max length 255 |
| name | string | required | max length 255; min length 1 |
| notes | string | optional | max length 2000 |
| reorderLevel | number | optional | minimum 0 |
| sku | string | required | max length 64; min length 1 |
| unit | string | required | one of: unit, kg, g, l, ml, m, hour, box, pack |
| unitCost | number | optional | minimum 0; at most 2 decimal place(s) |
| unitPrice | number | optional | minimum 0; at most 2 decimal place(s) |
/api/inventory/{itemId}Fetch one itemPath itemId
/api/inventory/{itemId}Update an itemPath itemId
Body — InventoryItemUpdate
| Field | Type | Required | Constraints |
|---|---|---|---|
| category | string | optional | one of: product, material, part, consumable, packaging, equipment, other |
| currency | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| description | string | optional | max length 2000 |
| location | string | optional | max length 255 |
| name | string | optional | max length 255; min length 1 |
| notes | string | optional | max length 2000 |
| reorderLevel | number | optional | minimum 0 |
| sku | string | optional | max length 64; min length 1 |
| unit | string | optional | one of: unit, kg, g, l, ml, m, hour, box, pack |
| unitCost | number | optional | minimum 0; at most 2 decimal place(s) |
| unitPrice | number | optional | minimum 0; at most 2 decimal place(s) |
/api/inventory/{itemId}Delete an itemPath itemId
/api/inventory/{itemId}/movementsMovement historyPath itemId
Query
- limit string
- offset string
/api/inventory/{itemId}/movementsRecord a movementPath itemId
Body — InventoryMovementAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| kind | string | required | must not equal "count"; one of: receipt, issue, adjustment, count |
| occurredAt | ISO date | optional | |
| quantityDelta | number | required | must not equal 0 |
| reason | string | optional | max length 2000 |
| unitCost | number | optional | minimum 0; at most 2 decimal place(s) |
/api/inventory/{itemId}/countRecord a stock countPath itemId
Body — InventoryCountAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| countedQuantity | number | required | minimum 0 |
| occurredAt | ISO date | optional | |
| reason | string | optional | max length 2000 |
/api/inventory/dashboard/statsStock value and low stockQuery
- currency string
Purchase requests
FROM PROFESSIONAL
What you intend to buy, with lines and a printable document.
/api/purchase-requestsList requestsQuery
- limit string
- offset string
- search string
- dateFrom string
- dateTo string
/api/purchase-requestsCreate a requestBody — PurchaseRequestAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| lines | array of PurchaseRequestLineAddition | required | at most 200 entries; at least 1 entry |
| description | string | required | max length 255; min length 1 |
| itemId | uuid | optional | |
| quantity | number | required | must be positive |
| sku | string | optional | max length 64 |
| unit | string | required | one of: unit, kg, g, l, ml, m, hour, box, pack |
| unitCost | number | required | minimum 0; at most 2 decimal place(s) |
| neededBy | ISO date | optional | |
| notes | string | optional | max length 2000 |
| requestedDate | ISO date | optional | |
| supplierName | string | optional | max length 255 |
/api/purchase-requests/{requestId}Fetch one requestPath requestId
/api/purchase-requests/{requestId}Update a requestPath requestId
Body — PurchaseRequestUpdate
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| lines | array of PurchaseRequestLineAddition | optional | at most 200 entries; at least 1 entry |
| description | string | required | max length 255; min length 1 |
| itemId | uuid | optional | |
| quantity | number | required | must be positive |
| sku | string | optional | max length 64 |
| unit | string | required | one of: unit, kg, g, l, ml, m, hour, box, pack |
| unitCost | number | required | minimum 0; at most 2 decimal place(s) |
| neededBy | ISO date | optional | |
| notes | string | optional | max length 2000 |
| requestedDate | ISO date | optional | |
| supplierName | string | optional | max length 255 |
/api/purchase-requests/{requestId}Delete a requestPath requestId
/api/purchase-requests/{requestId}/printDownload the PDFPath requestId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
Tasks and time
FROM PROFESSIONAL
Tasks and the hours logged against them. Both sit behind the one time-tracking flag, together with mileage below.
/api/tasksList tasksQuery
- limit string
- offset string
- search string
- customerId string
- dateFrom string
- dateTo string
- status string
/api/tasksCreate a taskBody — TaskAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| defaultHourlyRate | number | optional | minimum 0; at most 2 decimal place(s) |
| description | string | optional | max length 2000 |
| name | string | required | max length 255; min length 1 |
| status | string | optional | one of: open, closed |
/api/tasks/{taskId}Fetch one taskPath taskId
/api/tasks/{taskId}Update a taskPath taskId
Body — TaskAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| defaultHourlyRate | number | optional | minimum 0; at most 2 decimal place(s) |
| description | string | optional | max length 2000 |
| name | string | required | max length 255; min length 1 |
| status | string | optional | one of: open, closed |
/api/tasks/{taskId}Delete a taskPath taskId
/api/tasks/{taskId}/time-logsLog time against a taskPath taskId
Body — TimeLogAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| description | string | required | max length 2000; min length 1 |
| loggedOn | ISO date | optional | |
| minutes | integer | required | maximum 1440; minimum 1 |
/api/tasks/{taskId}/time-logs/{timeLogId}Update a time logPath taskIdtimeLogId
Body — TimeLogAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| description | string | required | max length 2000; min length 1 |
| loggedOn | ISO date | optional | |
| minutes | integer | required | maximum 1440; minimum 1 |
/api/tasks/{taskId}/time-logs/{timeLogId}Delete a time logPath taskIdtimeLogId
Mileage
FROM PROFESSIONAL
Trips, with an optional attachment on each record.
/api/mileageList tripsQuery
- limit string
- offset string
- search string
- customerId string
- dateFrom string
- dateTo string
- status string
/api/mileageRecord a tripBody — MileageAddition
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | required | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | required | |
| distance | number | required | must be positive; at most 2 decimal place(s) |
| distanceUnit | string | required | one of: mile, km |
| fromLocation | string | required | max length 255; min length 1 |
| purpose | string | optional | max length 2000 |
| ratePerUnit | number | required | minimum 0; at most 2 decimal place(s) |
| toLocation | string | required | max length 255; min length 1 |
| travelledOn | ISO date | required | |
| vehicle | string | required | max length 255; min length 1 |
| vehicleType | string | required | one of: car, van, truck, motorcycle |
/api/mileage/{mileageId}Fetch one tripPath mileageId
/api/mileage/{mileageId}Update a tripPath mileageId
Body — MileageUpdate
| Field | Type | Required | Constraints |
|---|---|---|---|
| currency | string | optional | one of: GBP, USD, EUR, CHF, CNY, PLN |
| customerId | uuid | optional | |
| distance | number | optional | must be positive; at most 2 decimal place(s) |
| distanceUnit | string | optional | one of: mile, km |
| fromLocation | string | optional | max length 255; min length 1 |
| purpose | string | optional | max length 2000 |
| ratePerUnit | number | optional | minimum 0; at most 2 decimal place(s) |
| toLocation | string | optional | max length 255; min length 1 |
| travelledOn | ISO date | optional | |
| vehicle | string | optional | max length 255; min length 1 |
| vehicleType | string | optional | one of: car, van, truck, motorcycle |
/api/mileage/{mileageId}Delete a tripPath mileageId
/api/mileage/{mileageId}/attachmentUpload an attachmentPath mileageId
Send the file as multipart/form-data. There is no JSON body on this request.
/api/mileage/{mileageId}/attachmentDownload the attachmentPath mileageId
Responds with application/pdf and a Content-Disposition: attachment header — not JSON.
/api/mileage/{mileageId}/attachmentRemove the attachmentPath mileageId
Payment links
ON EVERY PLAN
Mint the public checkout link for one of your invoices, so your own software can send it wherever it likes.
/api/invoices/{invoiceId}/payment-linkCreate or fetch the payment linkPath invoiceId
What comes back
Written by hand against the database entities — the API carries no runtime description of its return types, so a generated version of this would be a guess.
Invoices
- id
- number
- reference
- type
- status
- ksefStatus
- currency
- issueDate
- dueDate
- totalAmount
- totalVat
- correctionReason
- correctionType
- items
- customerId
- templateId
- lastSentAt
- createdAt
- updatedAt
Each row also embeds the full `customer` object and the `template` it was rendered with. `items` is the array of lines you sent, each with its computed `amount` (price × qty) and a 1-based `id` within the invoice. `totalAmount` includes VAT; `totalVat` is the VAT part of it. `ksefStatus` is not a column: the API adds the status of the invoice’s latest KSeF filing attempt, one of `queued`, `sending`, `accepted`, `rejected` or `failed`, or null for an invoice never sent to KSeF. `correctionReason` and `correctionType` are a Polish credit note’s KSeF correction (KOR) details: the reason (up to 256 characters) and when the correction takes effect, 1 at the corrected invoice’s date, 2 at the credit note’s own date (the default when omitted), 3 other. They are null on every other invoice and for non-Polish issuers, where anything sent is discarded. Create and update answer with the saved invoice plus `ksefQueued`, true when that save queued a KSeF filing (a non-draft save on an account that files to KSeF); a save never emails the client.
Single fetch differs. Fetching one invoice additionally loads `sourceInvoices` — the invoices this credit note was raised against — and `creditNotes`, the credit notes raised against this invoice. It also loads `advanceInvoices`: for a settlement invoice the advance invoices it deducts, for an advance invoice the earlier advances of the same order it lists (advance and settlement invoices are for Polish issuers only); `settlements`, the invoices that link this advance; and `advanceChain`, the linked advances plus every earlier advance they in turn list, each with its own `advanceInvoices`, so the whole chain can be totalled (empty when nothing is linked). `ksef` is null if the invoice was never sent to KSeF, otherwise it carries `status`, `ksefNumber` (once accepted), `environment` (`test`, `demo` or `prod`), `acceptedAt`, `canRetry`, `emailPending` (accepted, client email not sent yet), `upoAvailable` (KSeF’s official receipt, the UPO, is held; download it in the portal) and `attempts`, newest first, each with `status`, `referenceNumber`, `ksefNumber`, `errorCode`, `errorMessage`, `createdAt` and `acceptedAt`. When `canRetry` is true, sending the invoice again files a new attempt; sending it after a rejection, without editing it since, answers 409 `KSEF_RETRY_NOT_ALLOWED`. The filed XML is never returned. None of these is present on a list row.
Quotes
- id
- number
- reference
- status
- currency
- issueDate
- expiryDate
- totalAmount
- totalVat
- items
- notes
- customerId
- createdAt
- updatedAt
Each row embeds the `customer` object. Totals are computed from the lines on write, exactly as on an invoice.
Clients
- id
- name
- accountNumber
- phoneNumber
- website
- companyRegNumber
- companyTaxNumber
- primaryPerson
- billingAddress
- deliveryAddress
- paymentRemindersEnabled
- createdAt
- updatedAt
`primaryPerson` is an object of `firstname`, `lastname`, `email`; both addresses are objects of `line`, `city`, `state`, `postcode`, `country`. No related documents are loaded.
Receipts
- id
- number
- reference
- status
- currency
- issueDate
- totalAmount
- totalVat
- items
- notes
- customerId
- invoiceId
- createdAt
- updatedAt
Each row embeds the `customer` object.
Single fetch differs. Fetching one receipt additionally loads the whole `invoice` object it was issued against, where there is one. A list row carries only `invoiceId`.
Expenses
- id
- vendor
- description
- category
- reference
- spentAt
- currency
- netAmount
- vatAmount
- vatRate
- totalAmount
- billable
- customerId
- invoicedInvoiceId
- attachmentFilename
- attachmentContentType
- attachmentByteSize
- createdAt
- updatedAt
`invoicedInvoiceId` is set once the expense has been billed onto an invoice, and is null until then. The attachment columns describe the stored file; the bytes come from the download endpoint, never inline.
Single fetch differs. ⚠ Expenses are the one resource where the single fetch returns LESS than a list row, not more. A list row embeds the `customer` object; fetching one expense loads no relations at all, so it carries `customerId` and no `customer`. If you need the client details, take them from the list, or fetch the client by its id.
Inventory
- id
- sku
- name
- description
- category
- unit
- location
- quantityOnHand
- reorderLevel
- unitCost
- unitPrice
- currency
- notes
- createdAt
- updatedAt
Movements are a separate resource and are never embedded in an item. A movement row is `id`, `itemId`, `kind`, `quantityDelta`, `quantityAfter`, `unitCost`, `reason`, `occurredAt`, `createdAt` — `quantityAfter` is the running balance as at that movement.
Purchase requests
- id
- number
- supplierName
- requestedDate
- neededBy
- currency
- notes
- lines
- createdAt
- updatedAt
`lines` is loaded on both the list and the single fetch — a request is not useful without them. A purchase request carries no status column; there is nothing to filter or transition.
Tasks and time
- id
- name
- description
- status
- currency
- defaultHourlyRate
- customerId
- totalMinutes
- unInvoicedMinutes
- createdAt
- updatedAt
⚠ The list returns `TaskListRow`, which is the task entity PLUS two computed totals — it is a projection, not the bare entity. `totalMinutes` is everything ever logged; `unInvoicedMinutes` is the part not yet billed onto an invoice, and the two are meant to disagree: invoicing a partly-billed task bills only the remainder.
Single fetch differs. Fetching one task adds its `timeLogs` array, newest first, each entry being `id`, `taskId`, `description`, `loggedOn`, `minutes`, `invoicedInvoiceId`. That array is the only thing the single fetch adds — the embedded `customer` object is on list rows too.
Mileage
- id
- travelledOn
- vehicle
- vehicleType
- fromLocation
- toLocation
- purpose
- distance
- distanceUnit
- ratePerUnit
- currency
- customerId
- invoicedInvoiceId
- attachmentFilename
- attachmentContentType
- attachmentByteSize
- attachmentUploadedAt
- createdAt
- updatedAt
Each row embeds the `customer` object. `distanceUnit` and `vehicleType` are enumerations; `ratePerUnit` is money per unit of `distanceUnit`.
Payment links
- url
The only endpoint here that does not return a record. The response is a single-key object carrying the public, token-addressed payment page URL for that invoice. Calling it again for the same invoice returns the same link rather than minting a second one.
The responses that are not JSON
6 routes answer withapplication/pdf and aContent-Disposition: attachmentheader rather than a record — the print routes:
- /api/invoices/{invoiceId}/print
- /api/quotes/{quoteId}/print
- /api/receipts/{receiptId}/print
- /api/expenses/{expenseId}/attachment
- /api/purchase-requests/{requestId}/print
- /api/mileage/{mileageId}/attachment
2 routes takemultipart/form-data instead of a JSON body — the attachment uploads. The file goes in a form field named file:
- /api/expenses/{expenseId}/attachment
- /api/mileage/{mileageId}/attachment
Both lists are read from the generated reference, so they cannot drift from the routes above.
When a request is refused
| Status | What happened | What to do |
|---|---|---|
| 401 | The token is missing, unknown, revoked or expired. | Check the Authorization header. The API deliberately does not say which of the four it was — mint a new token if you are unsure. |
| 402 | Your plan does not include the API, or does not include the resource you asked for. | The body names the capability and the plan that includes it. |
| 403 | The endpoint exists but is not part of the API. | Settings, team, billing and token management are portal-only. Only the endpoints on this page accept a token. |
| 404 | No such record, or it belongs to another account. | These two answer identically on purpose, so ids cannot be probed. |
| 400 | The body failed validation. | The response lists the fields that were rejected. |
Errors come back as JSON with a readable message. Plan refusals also carry a code and the plan that includes the capability, so your software can tell "upgrade needed" apart from "you sent something wrong".
Get started with payed.me today
Join payed.me today to streamline your invoicing process and achieve greater efficiency in your business.