InvoiceHub
Skip to the guide

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.

  1. 01Find the client
  2. 02Issue the invoice
  3. 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:

sales:invoices:issue
Finds or creates the client, issues the invoice and queues the email. It does not allow arbitrary client edits, cancellation or deletion.
sales:invoices:read
Allows status checks and PDF downloads according to the selected member's visibility. Required for the tracking examples below.
legacy:access
Only for existing REST/MCP access and OAuth. Not needed for this integration and does not grant invoice issuance.

Configure your API key

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.

invoice.jsonJSON
{
  "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.

cURLSHELL
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.

201 CreatedJSON
{
  "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.

See every field and limit in OpenAPI

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.

clientJSON
{
  "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:

deliveryJSON
{
  "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.

GET /v1/sales/invoices/456SHELL
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

GET /v1/sales/invoices/456/pdfSHELL
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.

Need help with your integration?