Referencia completa de la API de facturación electrónica de RocketFactura
URL base: https://api.rocketfactura.com
Autenticación: Envía tu clave de API en el encabezado X-RF-API-Key . Usa una clave rf_test_ para el sandbox y una clave rf_live_ para producción. El registro de la cuenta y las rutas de arranque de la primera clave / perfil de emisor usan un token bearer de Cognito en su lugar.
Content-Type: application/json para todas las solicitudes, excepto la subida de certificados que usa multipart/form-data.
OpenAPI: Se sirve una especificación legible por máquina en GET /api/rf/openapi.json.
Los pasos 1-3 usan tu token bearer de Cognito para inicializar la cuenta. A partir del paso 4, cada llamada usa tu X-RF-API-Key.
1. Registra tu cuenta
Crea la cuenta dueña de tus facturas. Esta llamada se autentica con tu token bearer de Cognito (del inicio de sesión del panel), no con una clave de API — así es como inicializas.
curl -X POST https://api.rocketfactura.com/api/rf/public/signup \
-H "Authorization: Bearer <cognito-id-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "mi-empresa",
"business_name": "Mi Empresa SA",
"tax_id": "3101123456",
"country": "CR",
"email": "admin@miempresa.cr"
}'2. Genera una clave de API de sandbox
Crea tu primera clave (también autenticada con Cognito). Usa el modo "test" para el sandbox. La clave en texto plano se devuelve una sola vez — guárdala. Las claves rf_test_ usan el sandbox; las claves rf_live_ usan producción.
curl -X POST https://api.rocketfactura.com/api/rf/tenants/{tenantId}/api-keys \
-H "Authorization: Bearer <cognito-id-token>" \
-H "Content-Type: application/json" \
-d '{ "label": "sandbox", "mode": "test" }'
# → { "success": true, "data": { "key": "rf_test_..." } }3. Registra tu perfil de emisor
Indícale a RocketFactura quién emite. Para CR esto incluye la razón social, la identificación fiscal y (en la práctica) el correo del emisor + el código de actividad económica que exige la DGT.
curl -X POST https://api.rocketfactura.com/api/rf/tenants/{tenantId}/issuer-profiles \
-H "Authorization: Bearer <cognito-id-token>" \
-H "Content-Type: application/json" \
-d '{
"country_code": "CR",
"display_name": "Mi Empresa",
"legal_name": "Mi Empresa SA",
"tax_id": "3101123456"
}'4. Sube tu certificado de firma
Sube el .p12 (CR) con su contraseña. La contraseña es la del certificado del Banco Central — NO la de tu portal ATV. Esta y todas las llamadas posteriores usan tu X-RF-API-Key.
curl -X POST https://api.rocketfactura.com/api/rf/certificates \
-H "X-RF-API-Key: rf_test_..." \
-F "country=CR" \
-F "p12=@mi-certificado.p12" \
-F "password=<banco-central-cert-password>"5. Crea una factura y consulta hasta que quede sellada
Haz POST de la factura y luego consulta GET /invoices/:id. Primero devuelve pending/queued, pasa por submitted (Hacienda "procesando") y termina en stamped. Los webhooks invoice.submitted e invoice.stamped se disparan en el camino.
# create
curl -X POST https://api.rocketfactura.com/api/rf/invoices \
-H "X-RF-API-Key: rf_test_..." \
-H "Content-Type: application/json" \
-d @invoice.json
# → { "success": true, "data": { "id": "...", "status": "queued" } }
# poll until data.status === "stamped"
curl https://api.rocketfactura.com/api/rf/invoices/<id> \
-H "X-RF-API-Key: rf_test_..."Una factura nueva inicia en pending/queued y avanza de forma asíncrona. Consulta GET /invoices/:id o suscríbete a webhooks.
| Estado | Hacienda CR | ¿Terminal? | Qué hacer |
|---|---|---|---|
pending | — | No | Espera — se está preparando el envío. |
queued | — | No | Espera — en cola para el worker de envío. |
submitted | procesando | No | Espera / sigue consultando — Hacienda está procesando. |
stamped | aceptado | Sí | Listo — descarga el XML, la factura es válida legalmente. |
rejected | rechazado | Sí | Corrige el documento y emite una factura NUEVA. No reintentes. |
error | — | No | Fallo transitorio — se permite reintentar (POST /invoices/:id/retry). |
cancelled | — | Sí | La factura se anuló después del sellado. |
Los errores devuelven { success: false, error: { code, message } }.
| Código | Causa | Solución |
|---|---|---|
MISSING_API_KEY | La solicitud no incluye el encabezado X-RF-API-Key. | Agrega el encabezado: X-RF-API-Key: rf_test_... (o rf_live_...). |
INVALID_API_KEY | La clave es desconocida, está revocada o tiene un formato inválido. | Revisa el valor de la clave o genera una nueva en Configuración / Claves de API. |
MODE_MISMATCH | Se usó una clave de prueba con datos de producción, o viceversa. | Usa tu clave rf_test_ para el sandbox, o cambia test_mode en Configuración. |
VALIDATION_ERROR | El cuerpo de la solicitud no pasó la validación del esquema. | Revisa error.details — cada entrada indica el campo y el problema. Los campos usan snake_case con objetos anidados issuer/receiver. |
CERTIFICATE_PARSE_ERROR | No se pudo abrir el certificado subido. | Revisa la contraseña del P12 — la contraseña del certificado del Banco Central, no la de tu portal ATV. |
CR_HACIENDA_NOT_CONFIGURED | Al emisor de CR le falta configuración requerida de Hacienda (p. ej., correo del emisor o código de actividad). | Agrega issuer.email e issuer.activity_code, y completa el perfil del emisor. |
CR_DUPLICATE_CLAVE | Ya se envió una factura con la misma clave de CR. | Usa un folio_number nuevo — la clave se deriva de él. La factura anterior ya existe. |
TENANT_LIMIT_REACHED | La cuenta alcanzó el límite de su plan (p. ej., claves de API o volumen de facturas). | Elimina recursos sin usar o mejora el plan. |
NO_CERTIFICATE | No hay un certificado de firma activo para el país de la factura. | Sube un certificado con POST /certificates antes de crear facturas. |
Cada entrega incluye un encabezado X-RF-Signature: sha256=<hex> — un HMAC-SHA256 del cuerpo sin procesar de la solicitud, firmado con el secreto de tu suscripción. Verifícalo antes de confiar en el payload.
import crypto from 'crypto';
// Express handler. Requires the RAW request body (not JSON-parsed) to verify.
function verifyRfWebhook(req, res) {
const secret = process.env.RF_WEBHOOK_SECRET; // the whsec_... from create
const header = req.get('X-RF-Signature') || ''; // "sha256=<hex>"
const expected = crypto
.createHmac('sha256', secret)
.update(req.rawBody) // raw bytes, utf8
.digest('hex');
const provided = header.replace(/^sha256=/, '');
const ok =
provided.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
if (!ok) return res.status(401).end();
// signature valid — process req.body ({ event, timestamp, data })
res.status(200).end();
}