Examples by language
Code to copy and adapt. Every example reads the base URL from FD_API and the key from FD_API_KEY; pick a language and the whole page sticks to it.
Stamp a CFDI
Send the voucher in the json parameter, as a form (application/x-www-form-urlencoded). If it was stamped, the response is HTTP 200 and the UUID comes in cfdi.UUID. If not, it's HTTP 400 and code and message say why. The invoice.json file can be any of the JSON validator examples.
# 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@invoice.json"<?php
// Read the key from an environment variable; never put it in the code.
$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('invoice.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 "Stamped: " . $res['cfdi']['UUID'] . PHP_EOL;
} else {
echo "Rejected ({$res['code']}): {$res['message']}" . PHP_EOL;
}// Read the key from an environment variable; never put it in the code.
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("invoice.json", "utf8") }),
signal: AbortSignal.timeout(60_000),
});
const body = await res.json();
if (res.ok && body.code == 200) {
console.log("Stamped:", body.cfdi.UUID);
} else {
console.log(`Rejected (${body.code}): ${body.message}`);
}# Read the key from an environment variable; never put it in the code.
import os, requests
with open("invoice.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("Stamped:", body["cfdi"]["UUID"])
else:
print(f"Rejected ({body.get('code')}): {body.get('message')}")// Read the key from an environment variable; never put it in the code.
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("invoice.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($"Stamped: {body.GetProperty("cfdi").GetProperty("UUID")}");
else
Console.WriteLine($"Rejected ({body.GetProperty("code")}): {body.GetProperty("message")}");// Read the key from an environment variable; never put it in the code.
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("invoice.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() is 200 if it was stamped; the UUID is in cfdi.UUID of the body.
// With 400, code and message say why (read them with your JSON library).
System.out.println(res.statusCode() + " " + res.body());Check the folio balance
Use it to confirm the key and the URL belong to the same environment before stamping. It returns balance with your available folios.
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'];Cancel a CFDI
Parameters go in HTTP headers, not in the body: UUID, MOTIVO (02 if you don't send it) and FOLIOSUSTITUCION with reason 01. Reasons and responses are in the cancellation guide.
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 = the SAT accepted the requestr = 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 = the SAT accepted the request$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 = the SAT accepted the requestChecked against the production API v5 on 2026-11-03.