INVOICEHUB / REST API / v1
One request. Invoice, PDF and email.
Your system supplies the details. InvoiceHub finds the client, issues the invoice with the correct number and prepares the email. Get started with clear steps and ready-to-use examples.
- 01Find the client
- 02Issue the invoice
- 03Track the email
01 / Preparation
Before your first request
Prepare the company once. Every request then uses the same numbering, tax and accounting rules as issuance in the application.
- Complete the company's name, UIC, address and VAT status in Administration.
- Add an invoice number range. Choose a default range or supply range_id in the request.
- Add a bank account for bank payments. With more than one account, choose a default or supply bank_account_id.
- The member authorizing the integration must have active company access and permission to issue documents.
- The client must have an invoice address. Prepare a recipient email or contacts enabled to receive documents.
02 / Secure access
Access and API keys
Open Administration → API keys, create a named key for this integration and select a member of the same company. Select only the permissions you need:
Copy the key when you create it: it is displayed only once. Keep it in a secret manager, not public code, URLs or messages. Examples use the INVOICEHUB_API_KEY variable.
03 / Quick start
Your first invoice
POST/v1/sales/invoices
Save this JSON in invoice.json. Replace the client id and email with your own details. If you do not have an id yet, replace the client block with the UIC or VAT identity from the next section.
{
"client": {
"id": 123
},
"invoice": {
"payment_method": "bank",
"language": "bg",
"lines": [
{
"description": "Accounting services",
"quantity": "1",
"unit_price": "100.00",
"vat_rate": "20.00"
}
]
},
"delivery": {
"mode": "explicit",
"to_email": "[email protected]"
}
}
Prices, quantities and percentages are decimal strings in quotes, not JSON numbers or expressions. The server determines the number, totals, company and issuing member.
Load the key securely into the variable and send the request. YOUR_API_KEY is a placeholder, not a working key. Use your own unique reference for Idempotency-Key, such as the order number.
export INVOICEHUB_API_KEY="YOUR_API_KEY"
curl --fail-with-body --request POST "https://web.invoicehub.work/v1/sales/invoices" \
--header "Authorization: Bearer $INVOICEHUB_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order-2026-0001" \
--data-binary @invoice.json
What a successful response looks like
201 Created means the invoice is issued and the email is queued. This example is for a VAT-registered company at 20 percent. Actual rates and totals follow your company's settings.
{
"client": {
"id": 123,
"created": false,
"name": "Client Ltd",
"eik": "203078445",
"vat": null
},
"invoice": {
"id": 456,
"kind": "invoice",
"status": "issued",
"number": "0000000123",
"issue_date": "2026-10-10",
"event_date": "2026-10-10",
"due_date": null,
"currency": "EUR",
"subtotal": "100.00",
"vat_total": "20.00",
"total": "120.00",
"pdf_url": "/v1/sales/invoices/456/pdf"
},
"delivery": [
{
"id": 789,
"to_email": "[email protected]",
"status": "pending"
}
],
"warnings": []
}
Save invoice.id and client.id. The Location header points to the tracking URL. The warnings field shows accepted warnings, such as VAT adjustments to match the company's status.
Additional fields and formats
You can supply issue_date, event_date and due_date in YYYY-MM-DD format. Without issue_date, the current business date is used. Discounts, prices_include_vat, notes, vat_basis_text and mark_paid are also supported.
Currency is EUR. Up to 200 lines are allowed. language is bg or bg-en; unknown fields and client-supplied numbers or totals are refused.
04 / Recipient details
Clients and recipients
Existing client or creation by identity
Pass client: {"id": 123} if you have an id from an earlier response. For a first integration or a client with no known id, supply the exact name and UIC or VAT number. The client is looked up only in the current company and created if no exact identifier record exists.
{
"name": "Client Ltd",
"eik": "203078445",
"address": "Plovdiv, Bulgaria"
}
A name alone never selects a client. An address is required on creation; mol and vat are optional. Saved names, addresses and identifiers are not overwritten by this request. For clients without identifiers, use a pre-created record and its id.
One-off email or the client's contacts
delivery.mode = explicit sends to one supplied address. It does not create a persistent contact or enable tracking without previously recorded consent. Alternatively, replace delivery with:
{
"mode": "client_contacts"
}
client_contacts uses every contact enabled to receive documents. If none exist, the request is refused before issuance. Do not combine the two modes or omit delivery.
05 / After issuance
Delivery and retries
Issuance is complete before the response, but email is sent separately. Track the invoice with the read permission and the id from the response; 456 below is an example id.
curl --fail-with-body "https://web.invoicehub.work/v1/sales/invoices/456" \
--header "Authorization: Bearer $INVOICEHUB_API_KEY"
- pending / sending
- The email is queued or being sent. Do not issue another invoice while waiting.
- sent
- The email provider accepted the message. This does not yet confirm delivery to the recipient.
- delivered
- The recipient's mail server accepted the email. This does not mean the person read it.
- failed / bounced / complained
- Sending failed, the address rejected the email, or a spam complaint was received. Check the document and recipient in the application.
Download the issued PDF
curl --fail-with-body "https://web.invoicehub.work/v1/sales/invoices/456/pdf" \
--header "Authorization: Bearer $INVOICEHUB_API_KEY" \
--output invoice-456.pdf
The retry rule
- The same key and details return the original successful response with Idempotency-Replayed: true, without issuing again or inserting another email queue entry.
- Different details with the same already-used key return 409 idempotency_conflict. Check which operation was submitted rather than bypassing the refusal.
- Use GET for the current status, not a repeated POST. A successful repeated POST preserves the original response, including the email's initial status.
- For 409 request_in_progress, 429 or 503, wait for Retry-After and retry with the same key and body. For correctable validation failures with no issued document, fix the details before retrying.
06 / If something goes wrong
Errors and limits
A refusal contains code, detail and fields. Keep the code and operation reference for diagnosis, but never send your API key to support.
- 401
- A valid active API key is missing. Check the Bearer header and whether the key was rotated or revoked.
- 403
- The required key permission is missing or the member no longer has the necessary access. Check both settings in Administration.
- 404
- The client or invoice is not accessible to this company and member. Do not use an id from another company.
- 409
- Check code: conflicting identities, registry confirmation required, a different body for an already-used key, or an operation in progress. Retry automatically only for request_in_progress.
- 422
- Correct the fields listed in fields. Common causes include a missing address, an unsuitable VAT rate, bank account, exhausted number range, closed period or unknown field.
- 429 / 503
- A limit was reached or infrastructure is temporarily unavailable. Wait for Retry-After. If the response was lost, safely retry the same operation with the same key.
Rotate or revoke a key
Rotate the selected key in Administration, save the new one in your secret manager and update the integration. The old key stops working immediately. You can replay an already-successful operation with the new key and the same details if the member can still see the invoice.
An invoice-only key does not affect other integrations. Removing legacy:access, or rotating or revoking such a legacy key, invalidates the company's OAuth tokens. Check the permissions before acting.
07 / For your integration developer
OpenAPI reference
The public OpenAPI document is generated from the actual REST routes. It contains requests, responses, types, limits and examples, without private browser forms.
Swagger is reference-only: executing requests from the page is disabled to prevent accidentally issuing a real invoice. API operations themselves always require authentication.