Ejemplos por lenguaje
Código para copiar y adaptar. Todos los ejemplos leen la URL base de FD_API y la llave de FD_API_KEY; elige un lenguaje y la página entera se queda en él.
Timbrar un CFDI
Manda el comprobante en el parámetro json, como formulario (application/x-www-form-urlencoded). Si se timbró, la respuesta es HTTP 200 y el UUID viene en cfdi.UUID. Si no, es HTTP 400 y code y message dicen por qué. El archivo factura.json puede ser cualquiera de los ejemplos del validador de JSON.
# FD_API=https://sandbox-app.facturadigital.com.mx/api/v5
curl -sS -X POST "$FD_API/invoice/create" \
-H "X-Api-Key: $FD_API_KEY" \
--data-urlencode "json@factura.json"<?php
// Lee la llave de una variable de entorno; nunca la pongas en el código.
$ch = curl_init(getenv('FD_API') . '/invoice/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('FD_API_KEY')],
CURLOPT_POSTFIELDS => http_build_query(['json' => file_get_contents('factura.json')]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$res = json_decode(curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http === 200 && ($res['code'] ?? null) == 200) {
echo "Timbrado: " . $res['cfdi']['UUID'] . PHP_EOL;
} else {
echo "Rechazado ({$res['code']}): {$res['message']}" . PHP_EOL;
}// Lee la llave de una variable de entorno; nunca la pongas en el código.
import { readFile } from "node:fs/promises";
const res = await fetch(`${process.env.FD_API}/invoice/create`, {
method: "POST",
headers: { "X-Api-Key": process.env.FD_API_KEY },
body: new URLSearchParams({ json: await readFile("factura.json", "utf8") }),
signal: AbortSignal.timeout(60_000),
});
const body = await res.json();
if (res.ok && body.code == 200) {
console.log("Timbrado:", body.cfdi.UUID);
} else {
console.log(`Rechazado (${body.code}): ${body.message}`);
}# Lee la llave de una variable de entorno; nunca la pongas en el código.
import os, requests
with open("factura.json", encoding="utf-8") as f:
r = requests.post(
f"{os.environ['FD_API']}/invoice/create",
headers={"X-Api-Key": os.environ["FD_API_KEY"]},
data={"json": f.read()},
timeout=60,
)
body = r.json()
if r.status_code == 200 and str(body.get("code")) == "200":
print("Timbrado:", body["cfdi"]["UUID"])
else:
print(f"Rechazado ({body.get('code')}): {body.get('message')}")// Lee la llave de una variable de entorno; nunca la pongas en el código.
using System.Net.Http.Json;
using System.Text.Json;
var api = Environment.GetEnvironmentVariable("FD_API");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
http.DefaultRequestHeaders.Add("X-Api-Key", Environment.GetEnvironmentVariable("FD_API_KEY"));
var form = new FormUrlEncodedContent(new Dictionary<string, string> {
["json"] = await File.ReadAllTextAsync("factura.json")
});
var res = await http.PostAsync($"{api}/invoice/create", form);
var body = await res.Content.ReadFromJsonAsync<JsonElement>();
if (res.IsSuccessStatusCode && body.GetProperty("code").ToString() == "200")
Console.WriteLine($"Timbrado: {body.GetProperty("cfdi").GetProperty("UUID")}");
else
Console.WriteLine($"Rechazado ({body.GetProperty("code")}): {body.GetProperty("message")}");// Lee la llave de una variable de entorno; nunca la pongas en el código.
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.Duration;
var json = Files.readString(Path.of("factura.json"));
var req = HttpRequest.newBuilder(URI.create(System.getenv("FD_API") + "/invoice/create"))
.timeout(Duration.ofSeconds(60))
.header("X-Api-Key", System.getenv("FD_API_KEY"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString("json=" + URLEncoder.encode(json, StandardCharsets.UTF_8)))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
// res.statusCode() es 200 si se timbró; el UUID viene en cfdi.UUID del cuerpo.
// Con 400, code y message dicen por qué (léelos con tu librería de JSON).
System.out.println(res.statusCode() + " " + res.body());Consultar el saldo de folios
Sirve para revisar que la llave y la URL son del mismo ambiente antes de timbrar. Responde balance con tus folios disponibles.
curl -sS "$FD_API/account/balance" -H "X-Api-Key: $FD_API_KEY"const res = await fetch(`${process.env.FD_API}/account/balance`, {
headers: { "X-Api-Key": process.env.FD_API_KEY },
});
const { balance } = await res.json();r = requests.get(f"{os.environ['FD_API']}/account/balance",
headers={"X-Api-Key": os.environ["FD_API_KEY"]}, timeout=30)
balance = r.json()["balance"]$ch = curl_init(getenv('FD_API') . '/account/balance');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('FD_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$balance = json_decode(curl_exec($ch), true)['balance'];Cancelar un CFDI
Los parámetros van en encabezados HTTP, no en el cuerpo: UUID, MOTIVO (02 si no lo mandas) y FOLIOSUSTITUCION con el motivo 01. Los motivos y las respuestas están en la guía de cancelación.
curl -sS -X POST "$FD_API/invoice/cancel" \
-H "X-Api-Key: $FD_API_KEY" \
-H "UUID: 6FA90F39-2D7A-4F1C-9B3E-5C1D8A0E7B42" \
-H "MOTIVO: 02"const res = await fetch(`${process.env.FD_API}/invoice/cancel`, {
method: "POST",
headers: {
"X-Api-Key": process.env.FD_API_KEY,
UUID: "6FA90F39-2D7A-4F1C-9B3E-5C1D8A0E7B42",
MOTIVO: "02",
},
});
const body = await res.json(); // code 200 = el SAT aceptó la solicitudr = requests.post(f"{os.environ['FD_API']}/invoice/cancel", headers={
"X-Api-Key": os.environ["FD_API_KEY"],
"UUID": "6FA90F39-2D7A-4F1C-9B3E-5C1D8A0E7B42",
"MOTIVO": "02",
}, timeout=60)
body = r.json() # code 200 = el SAT aceptó la solicitud$ch = curl_init(getenv('FD_API') . '/invoice/cancel');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => '',
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('FD_API_KEY'),
'UUID: 6FA90F39-2D7A-4F1C-9B3E-5C1D8A0E7B42',
'MOTIVO: 02',
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = json_decode(curl_exec($ch), true); // code 200 = el SAT aceptó la solicitudRevisado contra la API v5 en producción el 2026-11-03.