Skip to main content
This walks through the full path from nothing to a SAT-stamped CFDI 4.0 against the hosted API at https://api.newinvoice.dev. Do it all in test mode (an sk_test_... key) — test-mode stamps hit the PAC’s test environment, so nothing here touches SAT production and no real CFDI is issued. When you’re ready for real invoices, swap the test key for a live one (see Going live).

0. Get your API key

Sign up at https://api.newinvoice.dev/signup and open the dashboard. Each organization gets a test key (sk_test_...) and a live key (sk_live_...); copy the test key — it’s shown once.
Every /v1 call needs Authorization: Bearer $API_KEY. The key’s mode scopes all data: a test key never sees live resources and vice-versa, and livemode appears on every resource.

1. Register a fiscal profile (emitter + CSD)

An invoice is issued by a fiscal profile: an RFC plus its CSD (Certificado de Sello Digital). Upload the .cer and .key files directly (multipart) with the private-key password; the RFC, serial and validity window are read from the certificate. The key material is AES-256-GCM encrypted at rest and never returned.
Multipart field names: csd_cer, csd_key, csd_password for the CSD, and fiel_cer, fiel_key, fiel_password if you also attach a FIEL. Prefer JSON? Send the same data as application/json with csd: { cer_base64, key_base64, password } — both content types are accepted.
Testing without a production CSD? Test mode tolerates expired and SAT test certificates, so you can onboard with a free SAT test CSD. Live mode rejects expired CSDs (422 fiscal_profile.csd_expired) — use a current CSD to go live.
The profile comes back on hold. A brand-new profile has not yet been authorized to stamp, so its response carries status: "requires_action" and a single next_actions entry telling you exactly what to do next:
On retrieve or list the same pending action appears as pending_actions. When the profile is authorized both arrays are [] and status is "active".

2. Sign the carta manifiesto

The PAC requires a signed carta manifiesto per RFC to authorize timbrado (invoice stamping) for that taxpayer — this is the SAT/PAC authorization, not the CSD. This API signs it for you; there is no browser portal to visit. The gate applies in test mode too, so you sign it once here before stamping. The manifiesto is signed with the taxpayer’s FIEL (e.firma):
The manifest endpoint accepts the FIEL inline as -F cer= -F key= -F password= (or fiel: { cer_base64, key_base64, password \} in JSON), or reuses a FIEL already stored on the profile. On success the manifiesto is signed once and for all: the profile flips to status: "active" (manifest_status: "signed"), next_actions/pending_actions empty out, and a fiscal_profile.manifest_signed webhook fires. Signing is idempotent — an “already signed” result is treated as success.
Skip this step and the very next stamp attempt returns 409 fiscal_profile.manifest_required (in both test and live), with remediation pointing back to this endpoint. Every stamp call checks the manifiesto, so you only ever sign it once per profile.

3. Create + stamp an invoice

Send friendly JSON. You do not send the issuer — it comes from the profile. Omit taxes and each item defaults to 16% IVA traslado; subtotal/total are computed and rounded to the currency’s decimals.
You get back status: "stamped", a real uuid (folio fiscal), total: "116.00", serie: "A", folio: "1", the sello_cfd/sello_sat, and xml_url/pdf_url.

Required fields, briefly

Common coherence rules the API enforces (returned as 422 cfdi.invalid_input with a path):
  • payment_method: "PPD" forces payment_form: "99" (Por definir).
  • Type P must not carry items or payment_method; it needs payments.
  • Type E should carry relation (type 01 nota de crédito or 03 devolución) + original UUID.
  • Receptor XAXX010101000 (público en general) requires global_information.
  • Non-MXN currency requires exchange_rate.

4. Draft, then stamp later (optional)

Pass "draft": true to build and sign without stamping; the invoice returns status: "draft". Stamp it when ready:

5. Fetch the artifacts

Idempotency

Send Idempotency-Key: <unique> on POSTs. A retry with the same key + identical body replays the stored response (idempotent-replayed: true) instead of stamping twice; a different body under the same key → 409 idempotency.key_reuse. The PAC’s own duplicate code 307 is also resolved as a success-replay of the prior stamp, so double submits never mint two CFDIs.

Next