Dev Portal
The Developer API lets third-party systems integrate with the Invoicely platform — manage customers, create and read invoices, and look up reference codes. All endpoints are versioned under /v1/external.
Try it
Call the API straight from this page. Enter your sandbox API key and secret, authenticate to get a bearer token, then pick an example request (or edit the method, path, and body) and send it. Responses are shown live, exactly as your integration will receive them.
Authentication
Authentication is a two-step flow:
- Exchange your API credentials for an access token. Your merchant API credentials — generated from the API Keys page in your merchant portal — are presented once to the token endpoint.
- Call every other endpoint with that token as a
Bearertoken.
This keeps your long-lived secret off the wire on every request; a leaked access token expires on its own within a couple of hours.
Get an access token
Send your credentials as headers (no request body):
X-API-Key: pk_live_your_api_key
X-API-Secret: sk_live_your_api_secretResponse
{
"accessToken": "eyJhbGciOiJIUzUxMiJ9...",
"tokenType": "Bearer",
"expiresIn": 7200,
"expiresAt": "2026-05-20T13:30:00Z"
}The token is valid for 2 hours (expiresIn is in seconds). Request a new one when it expires — a 401 on any endpoint means the token is missing, invalid, or expired.
Use the access token
Send it on every other /v1/external/** request:
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
Content-Type: application/jsonEvery request is automatically scoped to the organisation that owns the API key — you never pass an organisation id, and you can only ever see or modify your own data.
Managing API keys (merchant portal)
These endpoints are used by signed-in portal users (JWT), not by the API key.
| Method | Path | Description |
|---|---|---|
POST | /v1/organisations/api-keys/{organisationId}/generate | Generate / regenerate the key pair |
GET | /v1/organisations/api-keys/{organisationId} | Read the public key |
DELETE | /v1/organisations/api-keys/{organisationId} | Revoke the key pair |
Generate response — the secret is shown once and is stored only as a hash; it can never be retrieved again. Regenerate if it is lost (this invalidates the previous pair).
{
"organisationId": 42,
"apiKey": "pk_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"apiSecret": "sk_live_9z8y7x6w5v4u3t2s1r0q9z8y7x6w5v4u3t2s1r0q9z8y7x6w",
"warning": "Store the API secret now. It is shown only once and cannot be retrieved again. Regenerate if it is lost."
}Errors
Errors use a consistent JSON shape:
{
"timestamp": "2026-05-20 11:30:00",
"status": 400,
"message": "email is required"
}| Status | Meaning |
|---|---|
400 | Validation failed / malformed request |
401 | Invalid X-API-Key/X-API-Secret, or a missing/invalid/expired access token |
404 | Resource not found, or it does not belong to your organisation |
500 | Unexpected server error |
Reference codes
List HS codes
Search HS codes
Get one HS code
{ "code": "01012900", "description": "Live horses,nes" }List currencies
Returns ["USD", "ZWG"].
List tax codes
The tax codes applicable to your organisation. Use these values on fiscal invoice line items.
[
{
"taxID": 517,
"taxName": "Standard rated 15.5%",
"taxPercent": 15.5,
"taxValidFrom": "2024-01-01",
"taxValidTill": null
}
]Customers
Create a customer
{
"contactPerson": "Jane Doe",
"companyName": "Acme Pvt Ltd",
"email": "jane@acme.co.zw",
"phone": "+263772000000",
"buyerTIN": "1234567890",
"vatNumber": "220123456",
"customerType": "BUSINESS",
"province": "Harare",
"city": "Harare",
"street": "Samora Machel Ave",
"houseNo": "12",
"district": "Harare Central"
}customerType is BUSINESS or INDIVIDUAL. buyerTIN, when supplied, must be 10 digits.
Response
{
"id": 501,
"contactPerson": "Jane Doe",
"companyName": "Acme Pvt Ltd",
"email": "jane@acme.co.zw",
"phone": "+263772000000",
"buyerTIN": "1234567890",
"vatNumber": "220123456",
"customerType": "BUSINESS",
"province": "Harare",
"city": "Harare",
"street": "Samora Machel Ave",
"houseNo": "12",
"district": "Harare Central"
}List customers
Paged, most recent first.
Get a customer
Invoices (standard)
Create an invoice
{
"customerId": 501,
"salesPerson": "John Banda",
"subject": "Website development",
"invoicingDate": "2026-05-20",
"dueDate": "2026-06-03",
"terms": "DUE_ON_RECEIPT",
"customerNote": "Thank you for your business",
"termsAndConditions": "Payment due within 14 days",
"totalAmount": 1500.00,
"currency": "USD",
"items": [
{
"itemDescription": "Design & build",
"quantity": 1,
"price": 1500.00,
"totalAmount": 1500.00
}
]
}Response — includes the generated invoiceNumber, status, and line items.
{
"id": 9001,
"invoiceNumber": "0009001",
"customerId": 501,
"salesPerson": "John Banda",
"subject": "Website development",
"status": "UNPAID",
"paid": false,
"currency": "USD",
"totalAmount": 1500.00,
"invoicingDate": "2026-05-20",
"dueDate": "2026-06-03",
"customerNote": "Thank you for your business",
"termsAndConditions": "Payment due within 14 days",
"items": [
{ "id": 1, "itemDescription": "Design & build", "quantity": 1, "price": 1500.00, "totalAmount": 1500.00 }
]
}List invoices
Paged; customerId is optional. List responses omit items.
Get an invoice
Full invoice including items.
Edit an invoice
Updates an existing invoice. The body is the same shape as create, without items — editing an invoice does not change its line items. Returns the updated invoice (with items). 404 if the invoice does not belong to your organisation.
{
"customerId": 501,
"salesPerson": "John Banda",
"subject": "Website development (revised)",
"invoicingDate": "2026-05-20",
"dueDate": "2026-06-10",
"terms": "DUE_ON_RECEIPT",
"customerNote": "Updated scope",
"termsAndConditions": "Payment due within 14 days",
"totalAmount": 1800.00,
"currency": "USD"
}Fiscal invoices (ZIMRA)
Fiscal invoices are submitted to ZIMRA. Your organisation must have a configured fiscal device and certificate; otherwise creation fails.
Create a fiscal invoice
{
"customerId": 501,
"salesPerson": "John Banda",
"subject": "Consulting services",
"invoicingDate": "2026-05-20",
"customerNote": "Thank you",
"currency": "USD",
"taxIncludedInProductPrice": true,
"items": [
{
"itemDescription": "Advisory retainer",
"quantity": 1.0,
"taxId": 517,
"taxName": "Standard rated 15.5%",
"taxPercent": 15.5,
"hsCode": "99001000",
"price": 1000.00,
"totalAmount": 1000.00
}
]
}Obtain valid hsCode values from /v1/external/hs-codes. Tax identifiers (taxId, taxName, taxPercent) must match your organisation's ZIMRA tax configuration.
List fiscal invoices
Get a fiscal invoice
Delete a fiscal invoice advice
Deletes an unpaid fiscal invoice advice. Returns 204 No Content. Only advices that are still unpaid and not yet fiscalised can be deleted — otherwise 400. 404 if it does not belong to your organisation.
{fiscalInvoiceId} here is the advice id (as returned by create / list / get), the same id used by the GET and download endpoints.Pay a fiscal invoice (submit to ZIMRA)
Fiscalises the receipt and submits it to ZIMRA. The combined payment amounts should settle the invoice total.
{
"payments": [
{ "moneyTypeCode": "Cash", "paymentAmount": 600.00 },
{ "moneyTypeCode": "Card", "paymentAmount": 555.00 }
]
}moneyTypeCode is one of Cash, Card, MobileWallet, Coupon, Credit, BankTransfer, Other.
Response — 200 on success, 400 if ZIMRA rejects the submission (with errors).
{
"invoiceNumber": "00090012",
"receiptId": 88123,
"invoiceId": 9001,
"status": "SUBMITTED",
"message": "Receipt submitted",
"errors": null
}Download a fiscal invoice
Returns the fiscal invoice as a PDF (application/pdf).
Credit note (cancel a fiscalised invoice)
Reverses (cancels) a fiscalised invoice with ZIMRA and records the reason. Use this to refund or void a receipt.
{ "reason": "Customer returned goods" }{invoiceId} here is the fiscalised invoice id — the invoiceId returned by the pay endpoint — not the advice id. An invoice can only be cancelled within 12 months of its invoicing date.Response — same shape as the pay endpoint: 200 on success, 400 if ZIMRA rejects the submission (with errors).
Debit note (increase a fiscalised invoice)
Raises a debit note against a fiscalised invoice — increasing the amount owed — and submits it to ZIMRA. Each item adds to a line on the original invoice (referenced by its invoiceItemId).
{
"reason": "Additional services rendered",
"items": [
{
"invoiceItemId": 456,
"additionalItemPrice": 100.00,
"invoiceItemTotal": 1100.00,
"quantity": 1.0
}
],
"payments": [
{ "moneyTypeCode": "Cash", "paymentAmount": 100.00 }
]
}{invoiceId} here is the fiscalised invoice id returned by the pay endpoint, not the advice id. moneyTypeCode values are the same as for the pay endpoint.Response — same shape as the pay endpoint: 200 on success, 400 if ZIMRA rejects it.
Bulk operations
For migrations and high-volume integrations you can create and fiscalise many records in a single call. Batch endpoints are partial-success: each item is processed independently, so one bad item never fails the rest. They always return 200 with a summary envelope — inspect successCount/failureCount and the per-item results. Each result's index matches the position of the item in the array you sent.
400.Envelope
{
"total": 3,
"successCount": 2,
"failureCount": 1,
"results": [
{ "index": 0, "success": true, "data": { /* the created/processed record */ } },
{ "index": 1, "success": false, "error": "email is required" },
{ "index": 2, "success": true, "data": { /* ... */ } }
]
}Create invoices in bulk
{
"invoices": [
{ "customerId": 501, "salesPerson": "John Banda", "subject": "Website", "invoicingDate": "2026-05-20", "totalAmount": 1500.00, "currency": "USD", "items": [ { "itemDescription": "Design", "quantity": 1, "price": 1500.00, "totalAmount": 1500.00 } ] },
{ "customerId": 502, "salesPerson": "John Banda", "subject": "Hosting", "invoicingDate": "2026-05-20", "totalAmount": 200.00, "currency": "USD", "items": [ { "itemDescription": "Annual hosting", "quantity": 1, "price": 200.00, "totalAmount": 200.00 } ] }
]
}Each successful item's data is the full invoice (same shape as the single create endpoint).
Create fiscal invoices in bulk
Creates the advices only — submit them to ZIMRA with the bulk pay endpoint. Body is { "invoices": [ ... ] } where each item is the same shape as the single create-fiscal-invoice request.
Pay (fiscalise) in bulk
Each advice is submitted to ZIMRA individually. An item's data is the same response as the single pay endpoint; on a ZIMRA rejection the item is marked success: false and data.status is FAILED with data.errors populated.
{
"items": [
{ "fiscalInvoiceId": 9001, "payments": [ { "moneyTypeCode": "Cash", "paymentAmount": 1155.00 } ] },
{ "fiscalInvoiceId": 9002, "payments": [ { "moneyTypeCode": "Card", "paymentAmount": 600.00 }, { "moneyTypeCode": "Cash", "paymentAmount": 555.00 } ] }
]
}Example (cURL)
1. Exchange credentials for an access token
curl -X POST https://your-host/v1/external/auth/token \
-H "X-API-Key: pk_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" \
-H "X-API-Secret: sk_live_9z8y7x6w5v4u3t2s1r0q9z8y7x6w5v4u3t2s1r0q9z8y7x6w"
# => { "accessToken": "eyJhbGciOiJIUzUxMiJ9...", "tokenType": "Bearer", "expiresIn": 7200, ... }2. Call any endpoint with the token
curl -X POST https://your-host/v1/external/invoices \
-H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9..." \
-H "Content-Type: application/json" \
-d '{
"customerId": 501,
"salesPerson": "John Banda",
"subject": "Website development",
"invoicingDate": "2026-05-20",
"dueDate": "2026-06-03",
"totalAmount": 1500.00,
"currency": "USD",
"items": [
{ "itemDescription": "Design & build", "quantity": 1, "price": 1500.00, "totalAmount": 1500.00 }
]
}'