Public API v1

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.

Create a contactcURL
# 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
}
Start here

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

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

ScopeAllows
contacts:readRead and list contacts.
contacts:writeCreate, update and archive contacts.
products:readRead and list catalogue products.
products:writeCreate and update products.
invoices:readRead and list invoices, series and their PDFs.
invoices:writeCreate 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.

Step 1

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.

Step 2

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.

Retry with the same keycURL
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.

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

Reference

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

MethodPathScopeDescription
POST/v1/contactscontacts:writeCreate a contact (customer or supplier).
GET/v1/contactscontacts:readList contacts, with search and pagination.
GET/v1/contacts/{id}contacts:readGet a contact.
PUT/v1/contacts/{id}contacts:writeUpdate a contact.
DELETE/v1/contacts/{id}contacts:writeArchive a contact (its history is kept).

Products

MethodPathScopeDescription
POST/v1/productsproducts:writeCreate a catalogue product or service.
GET/v1/productsproducts:readList products, with search and pagination.
GET/v1/products/{id}products:readGet a product.
PUT/v1/products/{id}products:writeUpdate a product.

Invoice series

MethodPathScopeDescription
GET/v1/invoice-seriesinvoices:readList the available series (needed to issue).

Read-only in this version of the API: series are created and configured in the app.

Invoices

MethodPathScopeDescription
POST/v1/invoicesinvoices:writeCreate a draft. Requires Idempotency-Key.
POST/v1/invoices/{id}/issueinvoices:writeIssue the draft — number, record chain and submission to the Tax Agency.
GET/v1/invoicesinvoices:readList invoices, filtered by status, series, dates or contact.
GET/v1/invoices/{id}invoices:readGet an invoice, with its hash, QR code and status with the Tax Agency.
GET/v1/invoices/{id}/pdfinvoices:readDownload 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": "..." }.

CodeMeaning
400Invalid request — for example, Idempotency-Key is missing when creating an invoice.
401Key missing, malformed, revoked or expired.
403The key is valid but lacks the scope required for this route.
404The resource does not exist or does not belong to your account.
429Daily 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