Integrate your customers' Veri*Factu invoicing with a REST API.
Contacts, products, series and invoices — the same logic the Kullqi app uses, available over HTTP with an API key. Record chaining and submission to the Spanish Tax Agency apply in exactly the same way, wherever you call it from.
# replace sk_live_... with your own key, generated at app.kullqi.com/api-keys curl -X POST https://api.kullqi.com/v1/contacts \ -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "type": "CUSTOMER", "legalName": "Comercial Ejemplo SL", "taxId": "B12345674", "email": "facturacion@ejemplo.com" }' # response — 201 Created { "id": 42, "type": "CUSTOMER", "legalName": "Comercial Ejemplo SL", "taxId": "B12345674", "archived": false }
Authentication
Every request to /v1/** carries your key in the Authorization header, as a Bearer token. Generate and revoke keys from API keys in your account — only an ADMIN can do this, and the key is shown only once, when it is created (save it then; Kullqi only keeps its hash, never the value).
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Each key has one or more scopes — per-resource permissions, independent of the role of the user who created it. A request to an endpoint without the required scope gets 403 Forbidden.
| Scope | Allows |
|---|---|
| contacts:read | Read and list contacts. |
| contacts:write | Create, update and archive contacts. |
| products:read | Read and list catalogue products. |
| products:write | Create and update products. |
| invoices:read | Read and list invoices, series and their PDFs. |
| invoices:write | Create drafts and issue invoices. |
Invoice lifecycle
Issuing an invoice takes two deliberately separate steps: creating the draft never has any tax effect; issuing it does — it assigns the series number, chains the record and triggers the real submission to the Spanish Tax Agency (Veri*Factu). No endpoint combines both in a single call.
POST /v1/invoices
Creates a draft (status: "DRAFT") with its lines. No number and no tax effect; it can be read and discarded freely. Requires Idempotency-Key — see below.
POST /v1/invoices/{id}/issue
Issues the draft in a series (seriesId). From this point the invoice cannot be changed: number assigned, record chained and submission to the Tax Agency under way.
Mandatory idempotency when creating invoices
POST /v1/invoices requires the Idempotency-Key header — an identifier you generate (for example, one UUID per creation attempt from your system). Repeating the same request with the same key never creates a second draft: it returns the resource that was already created.
Without Idempotency-Key, the request fails with 400 Bad Request before touching anything — it is not optional. A real invoice cannot be undone with a simple DELETE, so a network retry on your side must never be able to duplicate it.
curl -X POST https://api.kullqi.com/v1/invoices \ -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Idempotency-Key: 8f14e45f-ceea-4c9c-8b3d-4a9e5f1c2b90" \ -H "Content-Type: application/json" \ -d '{ "contactId": 42, "invoiceType": "F1", "lines": [ { "description": "Consultoría", "quantity": 1, "unitPrice": 100.00, "taxRate": 21 } ] }' # first call → 201 Created, id: 17 # repeat EXACTLY the same Idempotency-Key → 200 OK, same id: 17
Rate limits
Designed for real integrations, not for scraping or replacing normal use of the app.
| Limit | Value |
|---|---|
| Daily quota per account (tenant) | 5,000 requests / calendar day |
| Sustained rate (gateway) | 20 requests / second |
| Maximum burst (gateway) | 40 requests |
Exceeding the daily quota returns 429 Too Many Requests with a { "message": "..." } body; the counter resets at midnight.
Available resources
All routes live under https://api.kullqi.com. The bodies and fields are exactly the same as those used by the Kullqi app itself — nothing simplified or cut down for the public API.
Contacts
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/contacts | contacts:write | Create a contact (customer or supplier). |
| GET | /v1/contacts | contacts:read | List contacts, with search and pagination. |
| GET | /v1/contacts/{id} | contacts:read | Get a contact. |
| PUT | /v1/contacts/{id} | contacts:write | Update a contact. |
| DELETE | /v1/contacts/{id} | contacts:write | Archive a contact (its history is kept). |
Products
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/products | products:write | Create a catalogue product or service. |
| GET | /v1/products | products:read | List products, with search and pagination. |
| GET | /v1/products/{id} | products:read | Get a product. |
| PUT | /v1/products/{id} | products:write | Update a product. |
Invoice series
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /v1/invoice-series | invoices:read | List the available series (needed to issue). |
Read-only in this version of the API: series are created and configured in the app.
Invoices
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/invoices | invoices:write | Create a draft. Requires Idempotency-Key. |
| POST | /v1/invoices/{id}/issue | invoices:write | Issue the draft — number, record chain and submission to the Tax Agency. |
| GET | /v1/invoices | invoices:read | List invoices, filtered by status, series, dates or contact. |
| GET | /v1/invoices/{id} | invoices:read | Get an invoice, with its hash, QR code and status with the Tax Agency. |
| GET | /v1/invoices/{id}/pdf | invoices:read | Download the PDF with the official verification QR code. |
Not in this version of the API: correcting, cancelling, recording payments and sending by email — for now only from the Kullqi app.
Errors
Standard HTTP status codes. Validation errors specific to /v1 return a simple JSON body: { "message": "..." }.
| Code | Meaning |
|---|---|
400 | Invalid request — for example, Idempotency-Key is missing when creating an invoice. |
401 | Key missing, malformed, revoked or expired. |
403 | The key is valid but lacks the scope required for this route. |
404 | The resource does not exist or does not belong to your account. |
429 | Daily quota or rate limit exceeded. |
Generate your key and make your first call
It is shown only once, when it is created — copy it then.
Go to API keys