curl --request POST \
--url https://api.newinvoice.dev/v1/validations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"xml_base64": "PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4="
}
'import requests
url = "https://api.newinvoice.dev/v1/validations"
payload = { "xml_base64": "PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({xml_base64: 'PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4='})
};
fetch('https://api.newinvoice.dev/v1/validations', 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.newinvoice.dev/v1/validations",
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([
'xml_base64' => 'PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4='
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.newinvoice.dev/v1/validations"
payload := strings.NewReader("{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.newinvoice.dev/v1/validations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.newinvoice.dev/v1/validations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}"
response = http.request(request)
puts response.read_body{
"object": "validation",
"id": "obj_2P9K3sample",
"valid": true,
"errors": [
{
"code": "string",
"path": "string",
"message": "string",
"remediation": "string"
}
],
"summary": {
"uuid": "5FB2822E-396D-4725-8521-CDC4BDD20CCF",
"version": "string",
"tipo_de_comprobante": "string",
"emisor": "string",
"receptor": "string",
"moneda": "MXN",
"sub_total": "string",
"total": "116.00",
"stamped": true,
"sat": {
"estado": "string",
"es_cancelable": "string",
"cancel_status": "none"
}
},
"livemode": false,
"created_at": "2026-07-10T18:25:43Z"
}{
"type": "https://errors.invoiceapi.mx/auth_unauthorized",
"title": "Authentication required",
"status": 401,
"code": "auth.unauthorized",
"detail": "Missing or invalid API key.",
"remediation": "Send `Authorization: Bearer sk_test_...` (or sk_live_...) with a valid API key.",
"doc_url": "/docs/errors#auth-unauthorized",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/idempotency_key_reuse",
"title": "Idempotency-Key reused with a different request body",
"status": 409,
"code": "idempotency.key_reuse",
"detail": "This Idempotency-Key was already used for a different payload.",
"remediation": "Reuse an Idempotency-Key only for byte-identical retries; use a fresh key otherwise.",
"doc_url": "/docs/errors#idempotency-key_reuse",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/request_validation_failed",
"title": "Request validation failed",
"status": 422,
"code": "request.validation_failed",
"detail": "One or more fields did not satisfy the schema.",
"remediation": "Fix the fields listed in `errors` and resubmit.",
"doc_url": "/docs/errors#request-validation_failed",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit.exceeded",
"detail": "Too many requests for this key + request class (reads and writes are limited separately).",
"remediation": "Back off and retry after the number of seconds in the Retry-After header; batch work or lower your request rate.",
"doc_url": "/docs/errors#rate_limit-exceeded",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/internal_error",
"title": "Internal server error",
"status": 500,
"code": "internal.error",
"detail": "An unexpected error occurred.",
"remediation": "Retry later; contact support with the request_id if it persists.",
"doc_url": "/docs/errors#internal-error",
"request_id": "req_2P9K3sample"
}Validate a CFDI
Validate a CFDI 4.0 XML: structure, arithmetic, the digital seal (sello), and — when already stamped — live SAT status. Send either JSON { "xml_base64": "..." } or a raw CFDI XML body with Content-Type application/xml.
curl --request POST \
--url https://api.newinvoice.dev/v1/validations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"xml_base64": "PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4="
}
'import requests
url = "https://api.newinvoice.dev/v1/validations"
payload = { "xml_base64": "PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({xml_base64: 'PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4='})
};
fetch('https://api.newinvoice.dev/v1/validations', 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.newinvoice.dev/v1/validations",
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([
'xml_base64' => 'PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4='
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.newinvoice.dev/v1/validations"
payload := strings.NewReader("{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.newinvoice.dev/v1/validations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.newinvoice.dev/v1/validations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"xml_base64\": \"PGNmZGk6Q29tcHJvYmFudGUgLi4uLz4=\"\n}"
response = http.request(request)
puts response.read_body{
"object": "validation",
"id": "obj_2P9K3sample",
"valid": true,
"errors": [
{
"code": "string",
"path": "string",
"message": "string",
"remediation": "string"
}
],
"summary": {
"uuid": "5FB2822E-396D-4725-8521-CDC4BDD20CCF",
"version": "string",
"tipo_de_comprobante": "string",
"emisor": "string",
"receptor": "string",
"moneda": "MXN",
"sub_total": "string",
"total": "116.00",
"stamped": true,
"sat": {
"estado": "string",
"es_cancelable": "string",
"cancel_status": "none"
}
},
"livemode": false,
"created_at": "2026-07-10T18:25:43Z"
}{
"type": "https://errors.invoiceapi.mx/auth_unauthorized",
"title": "Authentication required",
"status": 401,
"code": "auth.unauthorized",
"detail": "Missing or invalid API key.",
"remediation": "Send `Authorization: Bearer sk_test_...` (or sk_live_...) with a valid API key.",
"doc_url": "/docs/errors#auth-unauthorized",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/idempotency_key_reuse",
"title": "Idempotency-Key reused with a different request body",
"status": 409,
"code": "idempotency.key_reuse",
"detail": "This Idempotency-Key was already used for a different payload.",
"remediation": "Reuse an Idempotency-Key only for byte-identical retries; use a fresh key otherwise.",
"doc_url": "/docs/errors#idempotency-key_reuse",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/request_validation_failed",
"title": "Request validation failed",
"status": 422,
"code": "request.validation_failed",
"detail": "One or more fields did not satisfy the schema.",
"remediation": "Fix the fields listed in `errors` and resubmit.",
"doc_url": "/docs/errors#request-validation_failed",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit.exceeded",
"detail": "Too many requests for this key + request class (reads and writes are limited separately).",
"remediation": "Back off and retry after the number of seconds in the Retry-After header; batch work or lower your request rate.",
"doc_url": "/docs/errors#rate_limit-exceeded",
"request_id": "req_2P9K3sample"
}{
"type": "https://errors.invoiceapi.mx/internal_error",
"title": "Internal server error",
"status": 500,
"code": "internal.error",
"detail": "An unexpected error occurred.",
"remediation": "Retry later; contact support with the request_id if it persists.",
"doc_url": "/docs/errors#internal-error",
"request_id": "req_2P9K3sample"
}Authorizations
API key: Authorization: Bearer sk_test_... or sk_live_....
Headers
Pin a dated API release (Stripe-style). Omit to use the current version. An unknown value returns 400 request.invalid_version. Echoed back on every response.
"2026-07-10"
Connect: act on behalf of one of your connected accounts (its acct_…/org_… id). Omit to act as your own organization. Not honored on /v1/api_keys.
"org_2P9connectedacct"
Safely retry any POST. The first response is stored for 24h and replayed byte-for-byte for identical retries (the replay adds an idempotent-replayed: true header). Reusing the key with a different body is 409 idempotency.key_reuse; an in-flight duplicate is 409 idempotency.key_processing. This makes retrying a 502/429 safe — no duplicate stamp.
"a1b2c3d4-e5f6-4789-8abc-1234567890ab"
Body
Base64 of the CFDI XML to validate (Content-Type application/json).
1Response
Default Response
Always "validation".
validation Validation record id.
True when the CFDI passed all structural/SAT checks.
The validation failures found (empty when valid).
Show child attributes
Show child attributes
Parsed summary of the CFDI plus SAT status.
Show child attributes
Show child attributes
True if validated with a live-mode key.
Creation timestamp, ISO-8601 UTC.