curl --request POST \
--url https://api.timbrix.mx/invoices \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T22:50:00",
"use": "G03",
"items": [
{
"quantity": 1,
"amount": 100,
"productId": "<string>",
"description": "Servicio de consultoría",
"unitPrice": 100,
"productKey": "84111506",
"unitKey": "E48",
"unit": "E48",
"taxObject": "02",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.160000",
"base": 100,
"amount": 16,
"withholding": false
}
]
}
],
"type": "I",
"relatedInvoiceUuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"paymentForm": "01",
"paymentMethod": "PUE",
"currency": "MXN",
"exchange": 1,
"export": "01",
"customerId": "<string>",
"idempotencyKey": "order-8421-attempt-1"
}
'import requests
url = "https://api.timbrix.mx/invoices"
payload = {
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T22:50:00",
"use": "G03",
"items": [
{
"quantity": 1,
"amount": 100,
"productId": "<string>",
"description": "Servicio de consultoría",
"unitPrice": 100,
"productKey": "84111506",
"unitKey": "E48",
"unit": "E48",
"taxObject": "02",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.160000",
"base": 100,
"amount": 16,
"withholding": False
}
]
}
],
"type": "I",
"relatedInvoiceUuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"paymentForm": "01",
"paymentMethod": "PUE",
"currency": "MXN",
"exchange": 1,
"export": "01",
"customerId": "<string>",
"idempotencyKey": "order-8421-attempt-1"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
series: 'A',
folioNumber: '1',
date: '2026-07-30T22:50:00',
use: 'G03',
items: [
{
quantity: 1,
amount: 100,
productId: '<string>',
description: 'Servicio de consultoría',
unitPrice: 100,
productKey: '84111506',
unitKey: 'E48',
unit: 'E48',
taxObject: '02',
taxes: [
{
type: '002',
factorType: 'Tasa',
rate: '0.160000',
base: 100,
amount: 16,
withholding: false
}
]
}
],
type: 'I',
relatedInvoiceUuid: 'd3bfbc57-44af-4390-a064-f0afab85e5df',
paymentForm: '01',
paymentMethod: 'PUE',
currency: 'MXN',
exchange: 1,
export: '01',
customerId: '<string>',
idempotencyKey: 'order-8421-attempt-1'
})
};
fetch('https://api.timbrix.mx/invoices', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.timbrix.mx/invoices",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'series' => 'A',
'folioNumber' => '1',
'date' => '2026-07-30T22:50:00',
'use' => 'G03',
'items' => [
[
'quantity' => 1,
'amount' => 100,
'productId' => '<string>',
'description' => 'Servicio de consultoría',
'unitPrice' => 100,
'productKey' => '84111506',
'unitKey' => 'E48',
'unit' => 'E48',
'taxObject' => '02',
'taxes' => [
[
'type' => '002',
'factorType' => 'Tasa',
'rate' => '0.160000',
'base' => 100,
'amount' => 16,
'withholding' => false
]
]
]
],
'type' => 'I',
'relatedInvoiceUuid' => 'd3bfbc57-44af-4390-a064-f0afab85e5df',
'paymentForm' => '01',
'paymentMethod' => 'PUE',
'currency' => 'MXN',
'exchange' => 1,
'export' => '01',
'customerId' => '<string>',
'idempotencyKey' => 'order-8421-attempt-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.timbrix.mx/invoices"
payload := strings.NewReader("{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.timbrix.mx/invoices")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.timbrix.mx/invoices")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}"
response = http.request(request)
puts response.read_body{
"id": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
"uuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"status": "valid",
"type": "I",
"series": "A",
"folioNumber": "1",
"total": 116,
"date": "2026-07-30T22:50:00",
"xml": "<string>",
"createdAt": "2026-07-30T22:50:03.412Z",
"environment": "production"
}Create Invoice
Authenticate with either a Supabase session (send the X-Organization-Id header, must be a member of that organization) or an API key (external integrations, requires the write:invoices scope — the organization is resolved from the key). The customer can be sent inline (customer) or referenced by ID (customerId).
curl --request POST \
--url https://api.timbrix.mx/invoices \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T22:50:00",
"use": "G03",
"items": [
{
"quantity": 1,
"amount": 100,
"productId": "<string>",
"description": "Servicio de consultoría",
"unitPrice": 100,
"productKey": "84111506",
"unitKey": "E48",
"unit": "E48",
"taxObject": "02",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.160000",
"base": 100,
"amount": 16,
"withholding": false
}
]
}
],
"type": "I",
"relatedInvoiceUuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"paymentForm": "01",
"paymentMethod": "PUE",
"currency": "MXN",
"exchange": 1,
"export": "01",
"customerId": "<string>",
"idempotencyKey": "order-8421-attempt-1"
}
'import requests
url = "https://api.timbrix.mx/invoices"
payload = {
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T22:50:00",
"use": "G03",
"items": [
{
"quantity": 1,
"amount": 100,
"productId": "<string>",
"description": "Servicio de consultoría",
"unitPrice": 100,
"productKey": "84111506",
"unitKey": "E48",
"unit": "E48",
"taxObject": "02",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.160000",
"base": 100,
"amount": 16,
"withholding": False
}
]
}
],
"type": "I",
"relatedInvoiceUuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"paymentForm": "01",
"paymentMethod": "PUE",
"currency": "MXN",
"exchange": 1,
"export": "01",
"customerId": "<string>",
"idempotencyKey": "order-8421-attempt-1"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
series: 'A',
folioNumber: '1',
date: '2026-07-30T22:50:00',
use: 'G03',
items: [
{
quantity: 1,
amount: 100,
productId: '<string>',
description: 'Servicio de consultoría',
unitPrice: 100,
productKey: '84111506',
unitKey: 'E48',
unit: 'E48',
taxObject: '02',
taxes: [
{
type: '002',
factorType: 'Tasa',
rate: '0.160000',
base: 100,
amount: 16,
withholding: false
}
]
}
],
type: 'I',
relatedInvoiceUuid: 'd3bfbc57-44af-4390-a064-f0afab85e5df',
paymentForm: '01',
paymentMethod: 'PUE',
currency: 'MXN',
exchange: 1,
export: '01',
customerId: '<string>',
idempotencyKey: 'order-8421-attempt-1'
})
};
fetch('https://api.timbrix.mx/invoices', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.timbrix.mx/invoices",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'series' => 'A',
'folioNumber' => '1',
'date' => '2026-07-30T22:50:00',
'use' => 'G03',
'items' => [
[
'quantity' => 1,
'amount' => 100,
'productId' => '<string>',
'description' => 'Servicio de consultoría',
'unitPrice' => 100,
'productKey' => '84111506',
'unitKey' => 'E48',
'unit' => 'E48',
'taxObject' => '02',
'taxes' => [
[
'type' => '002',
'factorType' => 'Tasa',
'rate' => '0.160000',
'base' => 100,
'amount' => 16,
'withholding' => false
]
]
]
],
'type' => 'I',
'relatedInvoiceUuid' => 'd3bfbc57-44af-4390-a064-f0afab85e5df',
'paymentForm' => '01',
'paymentMethod' => 'PUE',
'currency' => 'MXN',
'exchange' => 1,
'export' => '01',
'customerId' => '<string>',
'idempotencyKey' => 'order-8421-attempt-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.timbrix.mx/invoices"
payload := strings.NewReader("{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.timbrix.mx/invoices")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.timbrix.mx/invoices")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"series\": \"A\",\n \"folioNumber\": \"1\",\n \"date\": \"2026-07-30T22:50:00\",\n \"use\": \"G03\",\n \"items\": [\n {\n \"quantity\": 1,\n \"amount\": 100,\n \"productId\": \"<string>\",\n \"description\": \"Servicio de consultoría\",\n \"unitPrice\": 100,\n \"productKey\": \"84111506\",\n \"unitKey\": \"E48\",\n \"unit\": \"E48\",\n \"taxObject\": \"02\",\n \"taxes\": [\n {\n \"type\": \"002\",\n \"factorType\": \"Tasa\",\n \"rate\": \"0.160000\",\n \"base\": 100,\n \"amount\": 16,\n \"withholding\": false\n }\n ]\n }\n ],\n \"type\": \"I\",\n \"relatedInvoiceUuid\": \"d3bfbc57-44af-4390-a064-f0afab85e5df\",\n \"paymentForm\": \"01\",\n \"paymentMethod\": \"PUE\",\n \"currency\": \"MXN\",\n \"exchange\": 1,\n \"export\": \"01\",\n \"customerId\": \"<string>\",\n \"idempotencyKey\": \"order-8421-attempt-1\"\n}"
response = http.request(request)
puts response.read_body{
"id": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
"uuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"status": "valid",
"type": "I",
"series": "A",
"folioNumber": "1",
"total": 116,
"date": "2026-07-30T22:50:00",
"xml": "<string>",
"createdAt": "2026-07-30T22:50:03.412Z",
"environment": "production"
}This endpoint takes noorganizationIdin the URL. Authenticate with either a Supabase Bearer session (send theX-Organization-Id: <org-id>header — the user must be a member of that organization) or an API key (the organization resolves automatically from the key; anyX-Organization-Idheader sent alongside an API key is ignored).
Authentication
Accepts either:- A Supabase Bearer session (
Authorization: Bearer <token>) with theX-Organization-Id: <org-id>header — the authenticated user must be a member of that organization. - An API key (
X-API-Key: sk_...) with thewrite:invoicesscope — the organization is resolved from the key itself.
PUT /organizations/:organizationId/legal) — configure that before creating your first invoice, or this endpoint returns a 400.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
series | string | Yes | Invoice series (e.g. A) |
folioNumber | string | Yes | Invoice folio number |
date | string | Yes | Issuance date-time, America/Mexico_City local time (no Z/offset) — see warning below |
paymentForm | string | Conditional*** | SAT forma de pago catalog code |
use | string | Yes | SAT uso CFDI catalog code |
customer | object | One of* | Inline customer — see below |
customerId | string | One of* | Existing customer ID (from the Customers resource) |
items | array | Yes | Line items — see below |
type | string | No | I (Ingreso), E (Egreso), or T (Traslado) — default: I |
relatedInvoiceUuid | string | Conditional** | UUID fiscal of the original Ingreso CFDI — required when type is E |
paymentMethod | string | No | PUE or PPD — default: PUE |
currency | string | No | Currency code — default: MXN |
exchange | number | No | Exchange rate — default: 1 |
export | string | No | SAT clave de exportación — default: 01 |
idempotencyKey | string | No | See Idempotency below |
customer or customerId is required — sending both, or neither, returns a 400.
** Required when type is E (Egreso/nota de crédito) — must be the UUID fiscal of an existing, vigente Ingreso CFDI belonging to the same organization; that invoice is the one being credited. Ignored for type: "I" and type: "T".
*** Required for type: "I" and type: "E". Not required — and discarded if sent — for type: "T" (Traslado), which carries no forma de pago.
subtotal and total are not request fields — they’re computed automatically from items.
date must be the current wall-clock time in Mexico City
(America/Mexico_City), not UTC. The SAT only accepts an issuance date
within roughly the last 72 hours (never in the future) and the PAC
compares your date value directly against its own Mexico City server
clock, with no timezone conversion. Sending a UTC timestamp (e.g.
new Date().toISOString()) looks ~6 hours in the future from that
reference and gets rejected — and confusingly, that rejection is often
reported back as CFDI40102 (“digest doesn’t match seal”) instead of a
clear date error. If you ever see CFDI40102, check this before
suspecting your CSD certificate.const date = new Date()
.toLocaleString("sv-SE", { timeZone: "America/Mexico_City" })
.replace(" ", "T")
date=$(TZ="America/Mexico_City" date +"%Y-%m-%dT%H:%M:%S")
customer (inline)
| Field | Type | Required | Description |
|---|---|---|---|
legalName | string | Yes | Customer legal name |
taxId | string | Yes | Customer RFC |
taxSystem | string | No | SAT régimen fiscal code — required unless the customer is público en general |
zip | string | No | Customer ZIP code |
items[]
Each line item can either reference an existing product/service by productId, or send the catalog fields inline (productKey, unitKey, description, unitPrice). Sending both, or neither, returns a 400.
| Field | Type | Required | Description |
|---|---|---|---|
quantity | number | Yes | Quantity |
amount | number | Yes | Line total (quantity * unitPrice) |
productId | string | One of* | Existing product/service ID — mutually exclusive with the inline fields below |
description | string | One of* | Line item description — required unless productId is provided |
unitPrice | number | One of* | Unit price — required unless productId is provided |
productKey | string | One of* | SAT product/service key — required unless productId is provided |
unitKey | string | One of* | SAT unit key — required unless productId is provided |
unit | string | No | Unit label — defaults to unitKey, or the product’s unit name when using productId |
taxObject | string | No | SAT objeto de impuesto code — defaults based on taxes |
taxes[] | array | No | Tax lines — type, factorType, rate, base, amount, withholding. Ignored (auto-resolved from the product’s configured taxes) when using productId. |
productId or the inline catalog fields (productKey/unitKey/description/unitPrice) is required per item.
taxes[]
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | SAT tax type catalog code (e.g. 002 = IVA) |
factorType | string | Yes | SAT factor type — Tasa, Cuota, or Exento |
rate | string | No | Tax rate as a decimal string (e.g. 0.160000) |
base | number | No | Taxable base — defaults to the item’s amount if omitted |
amount | number | No | Tax amount |
withholding | boolean | No | true = retención (Impuestos.Retenciones); false or omitted = traslado (Impuestos.Traslados) |
Business rules applied automatically
- The issuer (RFC, legal name, régimen fiscal, ZIP) is resolved from
organization_legal, matching how Facturapi resolves the issuer from the account profile instead of requiring it on every request. - When an item uses
productId, itsdescription,unitPrice,productKey,unitKey, and taxes are all resolved from the stored product — you only need to sendquantity,amount, andproductId. baseis required on every tax line in CFDI 4.0 — filled from the item’samountif omitted.subtotal/totalare computed fromitems[].amountanditems[].taxes[].amount.- Invoices to público en general (
customer.taxId = XAXX010101000,customer.legalName = PUBLICO EN GENERAL,type = I) automatically get the SATInformacionGlobalnode anduse = S01. - Traslado (
type = T) invoices are stamped withoutpaymentForm/paymentMethod, withsubtotalandtotalat0, and withuse = S01— these are overridden server-side, per CFDI 4.0 (Anexo 20), regardless of what the request sends. Every item’sunitPrice,amount, andtaxes[]are zeroed/dropped too (withtaxObject = 01), since CFDI 4.0 requiressubtotalto equal the sum of the item amounts;quantity,description,productKey, andunitKeyare kept. - The customer’s RFC, régimen fiscal, and uso CFDI are validated against the SAT catalog compatibility matrix before stamping (see Create Customer for the same validation rules).
docs/PAC_CFDI_CONTRACT.md in the repository for the internal payload contract this endpoint translates into before calling the PAC.
Idempotency
Stamping is not safe to blindly retry — if your request times out, you can’t tell whether the CFDI was actually stamped at the SAT before the timeout. Pass a uniqueidempotencyKey (e.g. your own order ID) and retry with the same key: if an invoice was already created for your organization with that key, the existing invoice is returned instead of stamping a second one.
{
"series": "A",
"folioNumber": "1",
"idempotencyKey": "order-8421",
"...": "..."
}
idempotencyKey, every request stamps a new CFDI — retries are your responsibility to avoid.
Example Request
curl -X POST https://api.timbrix.mx/invoices \
-H "Authorization: Bearer <your_token>" \
-H "X-Organization-Id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T15:50:00",
"paymentForm": "01",
"paymentMethod": "PUE",
"use": "G03",
"customerId": "590ce6c56d04f840aa8438af",
"items": [
{
"quantity": 1,
"amount": 100,
"productId": "60f1c3e-prod-8421"
}
]
}'
const invoice = await timbrix.invoices.create(
{
series: "A",
folioNumber: "1",
date: "2026-07-30T15:50:00",
paymentForm: "01",
paymentMethod: "PUE",
use: "G03",
customerId: "590ce6c56d04f840aa8438af",
items: [
{
quantity: 1,
amount: 100,
productId: "60f1c3e-prod-8421",
},
],
},
"550e8400-e29b-41d4-a716-446655440000"
)
console.log(invoice.uuid)
X-Organization-Id header (and the SDK’s organizationId argument) entirely — it’s resolved from the key:
const invoice = await timbrix.invoices.create({
series: "A",
folioNumber: "1",
date: "2026-07-30T15:50:00",
paymentForm: "01",
paymentMethod: "PUE",
use: "G03",
customerId: "590ce6c56d04f840aa8438af",
items: [
{
quantity: 1,
amount: 100,
productId: "60f1c3e-prod-8421",
},
],
})
console.log(invoice.uuid)
productId:
curl -X POST https://api.timbrix.mx/invoices \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"series": "A",
"folioNumber": "1",
"date": "2026-07-30T15:50:00",
"paymentForm": "01",
"paymentMethod": "PUE",
"use": "G03",
"customerId": "590ce6c56d04f840aa8438af",
"items": [
{
"quantity": 1,
"description": "Servicio de consultoría",
"unitPrice": 100,
"amount": 100,
"productKey": "84111506",
"unitKey": "E48",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.160000",
"amount": 16
}
]
}
]
}'
type: "E") — a credit note against a previously stamped Ingreso, referenced via relatedInvoiceUuid. The tax line below uses withholding: true to record a retención instead of a traslado:
curl -X POST https://api.timbrix.mx/invoices \
-H "Authorization: Bearer <your_token>" \
-H "X-Organization-Id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"series": "NC",
"folioNumber": "1",
"date": "2026-07-30T15:50:00",
"paymentForm": "01",
"paymentMethod": "PUE",
"use": "G02",
"type": "E",
"relatedInvoiceUuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"customerId": "590ce6c56d04f840aa8438af",
"items": [
{
"quantity": 1,
"description": "Bonificación por servicio de consultoría",
"unitPrice": 100,
"amount": 100,
"productKey": "84111506",
"unitKey": "E48",
"taxes": [
{
"type": "002",
"factorType": "Tasa",
"rate": "0.100000",
"amount": 10,
"withholding": true
}
]
}
]
}'
type: "T") — a comprobante that moves goods without transferring ownership, so there’s no relatedInvoiceUuid. A Traslado is not a sale: per CFDI 4.0 (Anexo 20) it carries no paymentForm/paymentMethod, its subtotal and total are stamped as 0, and use is forced to S01 (“Sin efectos fiscales”) server-side no matter what you send:
curl -X POST https://api.timbrix.mx/invoices \
-H "Authorization: Bearer <your_token>" \
-H "X-Organization-Id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"series": "T",
"folioNumber": "1",
"date": "2026-07-30T15:50:00",
"use": "S01",
"type": "T",
"customerId": "590ce6c56d04f840aa8438af",
"items": [
{
"quantity": 10,
"amount": 500,
"productId": "60f1c3e-prod-8421"
}
]
}'
amount/unitPrice are still accepted on a Traslado (the example above
sends amount: 500), but because the movement has no monetary value they are
stamped as 0 along with any taxes[] — so don’t be surprised when the
submitted amounts don’t appear in the resulting CFDI. Send them or omit them;
the stamped result is the same.Example Response
{
"id": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
"uuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
"status": "valid",
"type": "I",
"series": "A",
"folioNumber": "1",
"total": 116,
"date": "2026-07-30T15:50:00",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>",
"createdAt": "2026-07-30T15:50:03.412Z"
}
Common Errors
400 Bad Request
Invalid or incomplete invoice payload, missingX-Organization-Id header on a session-authenticated request, an issuing organization without complete fiscal data, sending both/neither customer and customerId, sending both/neither productId and the inline concept fields on an item, or a customer whose RFC/régimen/uso CFDI combination fails SAT catalog validation.
{
"statusCode": 400,
"message": "La organización no tiene datos fiscales completos (RFC, razón social, régimen fiscal y código postal). Complétalos en PUT /organizations/:organizationId/legal antes de timbrar.",
"error": "BAD_REQUEST"
}
400 can also carry a SAT catalog error embedded in message (e.g. "Error timbrado: CFDI40143 - Este RFC del receptor no existe..."). If you see CFDI40102 - El resultado de la digestión debe ser igual al resultado de la desencripción del sello, check the date field first (see the warning above) — it is frequently caused by sending it in UTC rather than Mexico City time, not by an actual certificate problem.
401 Unauthorized
Missing or invalid Bearer token / API key.403 Forbidden
The authenticated user is not a member of the organization sent inX-Organization-Id, or the API key does not have the write:invoices scope.
404 Not Found
customerId does not exist, or productId does not exist — in either case, belonging to a different organization than the authenticated one counts as not found.
503 Service Unavailable
The PAC did not respond after automatic retries (network failure, timeout, or a transient PAC error). Reported with thePAC_UNAVAILABLE error code. Safe to retry.
{
"statusCode": 503,
"message": "No se pudo conectar con el proveedor de timbrado (PAC). Intenta de nuevo en unos minutos.",
"error": "PAC_UNAVAILABLE",
"timestamp": "2026-07-30T15:50:03.412Z"
}
Authorizations
API Key for authentication (format: sk_...)
Headers
Required for Supabase session auth. Ignored when authenticating with an API key (the organization resolves from the key).
Body
"A"
"1"
"2026-07-30T22:50:00"
SAT uso CFDI catalog code
"G03"
Show child attributes
Show child attributes
Ingreso, Egreso, or Traslado (default: I)
I, E, T "I"
Required when type is E (Egreso/nota de crédito) — UUID fiscal of the original Ingreso CFDI it credits. Must belong to this organization and be a vigente Ingreso.
"d3bfbc57-44af-4390-a064-f0afab85e5df"
SAT forma de pago catalog code — required unless type is T (Traslado)
"01"
Default: PUE
PUE, PPD "PUE"
Default: MXN
"MXN"
Default: 1
1
SAT clave de exportación catalog code — default: 01
"01"
Show child attributes
Show child attributes
Existing customer ID — mutually exclusive with customer
Client-supplied key to safely retry this request without double-stamping. If an invoice was already created for this organization with the same key, that invoice is returned instead of stamping again.
"order-8421-attempt-1"
Response
Timbrix invoice record ID
"0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab"
Folio fiscal UUID asignado por el SAT vía PAC
"d3bfbc57-44af-4390-a064-f0afab85e5df"
valid "valid"
I = Ingreso, E = Egreso, T = Traslado
I, E, T "I"
"A"
"1"
116
"2026-07-30T22:50:00"
XML del CFDI timbrado (UTF-8)
"2026-07-30T22:50:03.412Z"
Ambiente en el que se timbró el CFDI. Los CFDI de sandbox no tienen validez fiscal ante el SAT.
sandbox, production "production"