ARCA exige firmar cada comprobante con un certificado digital. Factura SDK guarda los certificados con la clave privada cifrada y firma con el que tenga asignado el emisor. Podés dejar que Factura SDK cree el certificado en ARCA con la clave fiscal, o subir uno que ya tengas.
Endpoints
| Método y ruta | Qué hace |
|---|---|
POST /v1/certificates | Agrega un certificado al proyecto: creado en ARCA o subido. |
POST /v1/issuers/:id/certificate | Lo mismo, para un emisor: el certificado queda asignado al emisor. |
GET /v1/certificates | Lista los certificados. |
GET /v1/certificates/:id | Devuelve un certificado. |
POST /v1/certificates/:id/revoke | Revoca un certificado: deja de firmar. |
POST /v1/certificates/:id/authorize | Autoriza en ARCA un certificado para el web service de facturación, con la clave fiscal. |
El certificado
{
"id": "c3a1e7d9-8b2f-4e6a-a1d4-5f7b9c0e2d31",
"tax_id": "30715000004",
"alias": "facturasdk",
"source": "automation",
"serial": "6f3a9c2b1d4e5f60",
"expiration": "2028-10-14T14:02:00.000Z",
"status": "active",
"created_at": "2026-10-14T14:02:00.000Z",
"updated_at": "2026-10-14T14:02:00.000Z"
}
| Campo | Descripción |
|---|---|
id | Identificador del certificado. Es el certificate_id del emisor. |
tax_id | CUIT dueño del certificado. |
alias | Alias del certificado en ARCA. null si lo subiste sin alias y el certificado no lo trae. |
source | automation si lo creó Factura SDK en ARCA, manual si lo subiste. |
serial | Número de serie del certificado. null en los simulados del sandbox. |
expiration | Cuándo vence. Ver vencimiento. |
status | active, revoked o expired. El estado no cambia solo por la fecha: expired lo pone ARCA al rechazar el certificado. Ver vencimiento. |
La API nunca devuelve el certificado ni la clave privada: solo estos metadatos.
Cómo se usa
Los certificados son del proyecto. Cada emisor firma con el certificado de su certificate_id, y varios emisores pueden apuntar al mismo. Hay dos formas de organizarlo:
- Un certificado por emisor, del CUIT de cada uno. Es lo más simple cuando cada cliente te da su clave fiscal o su certificado. Ver asignar un certificado a un emisor.
- Un certificado compartido para todos los emisores. Lo creás una vez y lo asignás a cada emisor. Requiere que cada emisor te delegue el web service de facturación en ARCA y que aceptes esa delegación. Ver delegación.
Un emisor sin certificado, o con uno vencido o revocado, no puede verificarse ni emitir.
Crear un certificado en ARCA
Con source: "automation", Factura SDK entra a ARCA con la clave fiscal, crea el certificado y lo autoriza para el web service de facturación (wsfe) en el mismo trámite.
const response = await fetch('https://app.afipsdk.com/api/v1/certificates', {
method: 'POST',
headers: {
'Authorization': 'Bearer <ACCESS_TOKEN>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source: 'automation',
tax_id: '30715000004',
username: '30715000004',
password: '<CLAVE_FISCAL>',
alias: 'facturasdk'
})
});
const { automation_id } = await response.json();
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
source | string | Sí | automation. |
tax_id | string | Sí | CUIT dueño del certificado, 11 dígitos. En POST /v1/issuers/:id/certificate no se manda: es el del emisor. |
username | string | Sí | Usuario de ARCA (normalmente el mismo CUIT), al menos 4 caracteres. |
password | string | Sí | Clave fiscal, al menos 4 caracteres. No se guarda. |
alias | string | No | Alias del certificado en ARCA, alfanumérico, hasta 100 caracteres. Por defecto facturasdk. |
Ver como obtener la respuesta de una Automatización
{
"id": "a9e2c4b6-1d3f-4a5b-8c7d-2e4f6a8b0c1d",
"status": "complete",
"data": {
"certificate": {
"id": "c3a1e7d9-8b2f-4e6a-a1d4-5f7b9c0e2d31",
"tax_id": "30715000004",
"alias": "facturasdk",
"source": "automation",
"serial": "6f3a9c2b1d4e5f60",
"expiration": "2028-10-14T14:02:00.000Z",
"status": "active",
"created_at": "2026-10-14T14:02:00.000Z",
"updated_at": "2026-10-14T14:02:00.000Z"
},
"created": true,
"authorization": "created"
}
}
created es false si ese mismo certificado ya estaba cargado; authorization es created o exists según ya estuviera autorizado para wsfe, o null si ARCA no informó el estado. Cuando lo creás para un emisor, certificate trae además issuer_id y delegation (own si el certificado es del CUIT del emisor, delegated si es de otro).
Si la clave fiscal es incorrecta la automatización termina en error. En sandbox podés simular este caso enviando password: "INVALID_PASSWORD".
Subir un certificado
Si ya tenés un certificado de ARCA (por ejemplo, uno generado con nuestra herramienta), mandá el .crt y la .key en PEM. Es el modo por defecto (source: "manual"). El CUIT y el alias se leen del certificado.
import { readFileSync } from 'node:fs';
const response = await fetch('https://app.afipsdk.com/api/v1/certificates', {
method: 'POST',
headers: {
'Authorization': 'Bearer <ACCESS_TOKEN>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
cert: readFileSync('certificado.crt', 'utf8'),
key: readFileSync('clave.key', 'utf8')
})
});
const certificate = await response.json();
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
cert | string | Sí | Certificado en PEM (-----BEGIN CERTIFICATE-----), hasta 20.000 caracteres. En sandbox, VALID_CERT. |
key | string | Sí | Clave privada en PEM, hasta 20.000 caracteres. En sandbox, VALID_KEY. |
alias | string | No | Pisa el alias que trae el certificado. Alfanumérico, hasta 100 caracteres. |
tax_id | string | Condicional | Solo si el certificado no trae el CUIT. 11 dígitos. En sandbox es obligatorio. |
El certificado tiene que estar autorizado en ARCA para el web service wsfe del CUIT que va a facturar; si no lo está, la verificación del emisor va a fallar: podés autorizarlo desde acá.
En POST /v1/issuers/:id/certificate la respuesta trae además issuer_id y delegation (own si el certificado es del CUIT del emisor, delegated si es de otro).
Errores 400 con data_errors:
| Campo | Motivo |
|---|---|
cert | No se pudo leer el PEM, o el certificado ya venció. |
key | La clave privada no corresponde al certificado, o el certificado ya está cargado con otra clave. |
tax_id | No coincide con el CUIT del certificado, o falta y el certificado no lo trae. |
alias | No es alfanumérico o pasa los 100 caracteres. |
source | No es manual ni automation. |
username, password | Faltan o son más cortos que 4 caracteres, en el modo automático. |
Certificados simulados
En sandbox nada se firma, así que el certificado se simula con valores fijos. POST /v1/certificates y POST /v1/issuers/:id/certificate aceptan solo estos:
| Campo | Valor | Qué pasa |
|---|---|---|
cert | VALID_CERT | Certificado válido. Se guarda con el alias que mandes o, si no, facturasdk, y vence en dos años. |
cert | INVALID_CERT | 400: “El certificado no es válido”. |
cert | EXPIRED_CERT | 400: “El certificado venció el …“. |
key | VALID_KEY | Clave válida. |
key | INVALID_KEY | 400: “La clave privada no corresponde al certificado”. |
Solo VALID_CERT con VALID_KEY se guarda; las otras combinaciones responden el mismo error que daría un archivo real con ese problema, para que pruebes cómo lo maneja tu integración. Cualquier otro valor responde 400. En POST /v1/certificates el tax_id es obligatorio (todos los certificados simulados son el mismo texto, el CUIT los distingue); en POST /v1/issuers/:id/certificate se toma del emisor si no lo mandás, y con otro CUIT el certificado queda como delegado (delegation: "delegated").
{
"tax_id": "20111111112",
"cert": "VALID_CERT",
"key": "VALID_KEY"
}
El modo automático también funciona en sandbox: con un usuario y una clave cualesquiera (de al menos 4 caracteres) crea un VALID_CERT al instante, salvo con password: "INVALID_PASSWORD", que simula una clave fiscal incorrecta. En sandbox un certificado nunca pasa a expired: no hay ARCA que lo rechace.
Asignar un certificado a un emisor
El emisor firma con el certificado de su certificate_id:
POST /v1/issuers/:id/certificatecrea el certificado (automático o subido) y lo asigna en el mismo paso.certificate_idenPOST /v1/issuersoPUT /v1/issuers/:idasigna uno ya existente. Tiene que estaractive.
Si el certificado es de otro CUIT, además hay que autorizar la delegación. GET /v1/issuers?certificate_id= lista los emisores que firman con un certificado.
Revocar
POST /v1/certificates/:id/revoke deja el certificado en revoked: no firma más. Los emisores que apuntan a él dejan de poder emitir (400 en POST /v1/invoices) hasta que les asignes otro. Es idempotente: revocar uno ya revocado responde 200 con el certificado. Uno expired también se puede revocar y pasa a revoked.
const response = await fetch('https://app.afipsdk.com/api/v1/certificates/<CERTIFICATE_ID>/revoke', {
method: 'POST',
headers: {
'Authorization': 'Bearer <ACCESS_TOKEN>'
}
});
const certificate = await response.json();
Revocar en Factura SDK no lo revoca en ARCA: solo deja de usarse acá.
Autorizar para wsfe
Un certificado solo sirve para emitir si en ARCA está autorizado para el web service de facturación (wsfe). Los que crea Factura SDK con source: "automation" quedan autorizados al crearse. Uno que subiste a mano puede no estarlo: la verificación del emisor falla entonces con el código web_service_not_authorized. POST /v1/certificates/:id/authorize lo autoriza entrando a ARCA con la clave fiscal del CUIT del certificado. Los otros problemas del certificado que ARCA detecta al firmar tienen su propio código en la verificación y no se arreglan con esto: certificate_expired (vencido), certificate_key_mismatch (la clave privada no corresponde), certificate_untrusted (no lo emitió ARCA) y certificate_invalid (no se pudo leer); para todos ellos hace falta un certificado nuevo.
{
"username": "20111111112",
"password": "clave-fiscal"
}
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
username | string | No | Usuario de ARCA, al menos 4 caracteres. Por defecto, el CUIT del certificado. |
password | string | Sí | Clave fiscal, al menos 4 caracteres. No se guarda. |
Ver como obtener la respuesta de una Automatización
{
"id": "a9e2c4b6-1d3f-4a5b-8c7d-2e4f6a8b0c1d",
"status": "complete",
"data": {
"certificate": { "id": "3b7c1d2e-4f5a-4b6c-8d9e-0f1a2b3c4d5e", "tax_id": "20111111112", "alias": "facturasdk", "status": "active", "...": "..." },
"authorization": "created"
}
}
authorization es created, o exists si ya estaba autorizado. Si el certificado es de otro CUIT que el del emisor, lo que hace falta es la delegación, no esto. Si el certificado está revoked o expired, la respuesta es 400 (“El certificado … está revocado: no se puede autorizar. Creá uno nuevo.”, con data_errors.certificate_id). En sandbox es simulado; password: "INVALID_PASSWORD" simula la clave incorrecta.
Vencimiento
Los certificados de ARCA vencen a los dos años. expiration dice cuándo. En producción, Factura SDK avisa por email a los dueños del proyecto 30 y 7 días antes del vencimiento.
Si al emitir ARCA responde que el certificado venció, el certificado pasa a expired y no se usa más: el comprobante queda failed con la causa en last_error (y el código certificate_expired en la verificación de un emisor). Es final: para seguir emitiendo hay que crear un certificado nuevo.
Un certificado vencido no firma aunque siga active en los listados: Factura SDK mira expiration antes de firmar, y los emisores que lo usan dejan de emitir. Para renovarlo, creá un certificado nuevo, con POST /v1/issuers/:id/certificate (queda asignado) o con POST /v1/certificates y PUT /v1/issuers/:id con el nuevo certificate_id, y repetí la delegación si corresponde. Los comprobantes ya emitidos no se ven afectados.
Consultar y listar
GET /v1/certificates/:id devuelve un certificado; 404 si no existe.
GET /v1/certificates lista los certificados, con paginación por cursor.
| Parámetro | Tipo | Descripción |
|---|---|---|
tax_id | string | CUIT dueño del certificado, 11 dígitos. |
status | string | active, revoked o expired. |
limit | integer | Cantidad por página: por defecto 10, hasta 100. Un valor fuera de rango se ajusta. |
cursor | string | El next_cursor de la página anterior. |
const response = await fetch('https://app.afipsdk.com/api/v1/certificates?status=active', {
headers: {
'Authorization': 'Bearer <ACCESS_TOKEN>'
}
});
const { data, next_cursor, total_count } = await response.json();
{
"data": [
{ "id": "c3a1e7d9-8b2f-4e6a-a1d4-5f7b9c0e2d31", "tax_id": "30715000004", "alias": "facturasdk", "status": "active", "...": "..." }
],
"next_cursor": null,
"total_count": 1
}
Errores
| HTTP | Cuándo |
|---|---|
400 | Errores de forma (data_errors); certificado inválido o vencido; clave que no corresponde; tax_id que no coincide o falta; en sandbox, un valor que no es uno de los simulados; en automático, falta tax_id; autorizar o delegar un certificado revoked o expired; delegación no aceptada todavía en ARCA (data_errors.delegation); suscripción vencida o impaga; proyecto que no es de Factura SDK. |
401 | Access token inválido, ausente o revocado. |
404 | El certificado no existe. |
409 | Dos solicitudes cargaron el mismo certificado a la vez (“El certificado ya está cargado en … en este proyecto.”). Repetí y vas a recibir el existente. |
El formato de cada código y cómo consultar una automatización están en Errores y respuestas.