Skip to main content
The whole flow runs on the built-in simulated PAC, so you need no credentials to try it. Start the API:
pnpm install
pnpm dev            # http://localhost:3000, simulated PAC, PGlite — no env needed
1

Bootstrap an organization

Returns your first sk_test_ / sk_live_ keys (dev-gated endpoint).
API_KEY=$(curl -s -X POST localhost:3000/v1/organizations \
  -H 'content-type: application/json' \
  -d '{"name":"Acme"}' | jq -r '.api_keys.test')
2

Register a fiscal profile — upload the CSD files

No base64 needed: upload the .cer/.key directly. The bundled SAT test CSD lives at test/cfdi/fixtures/ (RFC AAA010101AAA, password 12345678a). Keys are AES-256-GCM encrypted at rest and never returned.
FP_ID=$(curl -s -X POST localhost:3000/v1/fiscal_profiles \
  -H "authorization: Bearer $API_KEY" \
  -F "legal_name=ACCEM SERVICIOS EMPRESARIALES SC" \
  -F "tax_regime=601" -F "zip=26015" -F "csd_password=12345678a" \
  -F "csd_cer=@test/cfdi/fixtures/csd.cer" \
  -F "csd_key=@test/cfdi/fixtures/csd.key" | jq -r '.id')
Prefer JSON? Send csd: { cer_base64, key_base64, password } as application/json.
The profile comes back on holdstatus: "requires_action" with a sign_manifest action under next_actions — and cannot stamp until its carta manifiesto is signed.
3

Sign the carta manifiesto

Prodigia requires a signed carta manifiesto per RFC to authorize timbrado. This API signs it for you (no browser portal), and the gate applies in test too — the simulated PAC signs it. The manifiesto is signed with the taxpayer’s FIEL (e.firma); in simulated mode the fixture CSD doubles as the FIEL, so reuse the same files. The profile then flips to status: "active".
curl -s -X POST localhost:3000/v1/fiscal_profiles/$FP_ID/manifest \
  -H "authorization: Bearer $API_KEY" \
  -F "cer=@test/cfdi/fixtures/csd.cer" \
  -F "key=@test/cfdi/fixtures/csd.key" \
  -F "password=12345678a" \
  -F "email=legal@example.mx" | jq '.status'
Skip this and the next stamp returns 409 fiscal_profile.manifest_required.
4

Create + stamp an invoice

The issuer is taken from the profile; you send the customer and line items.
curl -s -X POST localhost:3000/v1/invoices \
  -H "authorization: Bearer $API_KEY" -H 'content-type: application/json' \
  -H "idempotency-key: quickstart-1" \
  -d "{\"fiscal_profile_id\":\"$FP_ID\",\"payment_form\":\"03\",\"payment_method\":\"PUE\",\"use\":\"G03\",\"customer\":{\"rfc\":\"URE180429TM6\",\"name\":\"UNIVERSIDAD ROBOTICA ESPANOLA\",\"zip\":\"65000\",\"tax_regime\":\"601\"},\"items\":[{\"product_key\":\"01010101\",\"unit_key\":\"H87\",\"description\":\"Servicio de consultoría\",\"quantity\":1,\"unit_price\":\"100.00\"}]}" | jq
The response has status: "stamped", the fiscal uuid, and xml_url / pdf_url.

Next steps

Stamping in depth

Taxes, discounts, tax-included pricing, credit notes, and complemento de pago.

Go multi-tenant

Hold many clients under one platform key with the Invoice-Account header.

Webhooks

Receive signed events and verify them (JS + Python snippets).

Going live

Live keys, a real CSD, the carta manifiesto, and the production checklist.