Guía de uso de la API

REST JSON para registrar usuarios, administrar negocios emisores y solicitar comprobantes electrónicos ARCA/AFIP. Pensada para integrarla desde otro sistema.

JSON Bearer token CORS abierto Emisión asíncrona

Introducción

Base preferida: /api

Si /api no responde (por ejemplo si Apache enruta esa ruta a otra app), usá el fallback:

Fallback
/api.php?path=usuario/me

/api/usuario/... y /api/usuarios/... son equivalentes.

Autenticación

Todos los endpoints requieren token, excepto el registro de usuario y el GET raíz.

Enviá el token en uno de estos headers:

Headers
Authorization: Bearer <api_token>
X-Api-Token: <api_token>

También se acepta ?api_token= en la query; no lo uses en producción.

El token se obtiene al registrar un usuario, con GET /api/usuario/me, o desde Configuración en el panel web. Regenerarlo invalida el anterior.

Convenciones

Request

Content-Type: application/json

También se acepta form-urlencoded y multipart/form-data (archivos de certificado).

Override de método: POST + header X-HTTP-Method-Override: PUT.

Response

Siempre JSON UTF-8.

{ "ok": true, ... }
{ "ok": false, "error": "mensaje" }

Códigos: 200/201 OK · 400 validación · 401 token · 404 · 409 conflicto · 500.

Flujo típico

  1. POST /api/usuario o usar un token ya generado en Configuración.
  2. POST /api/negocios y luego cargar certificado AFIP con /credenciales.
  3. POST /api/comprobantes → guardar el id (queda en estado solicitado).
  4. Polling a GET /api/comprobantes/{id} hasta emitido o rechazado.
  5. Si está emitido, usar cae, numero y pdf_path.
La autorización en ARCA no es inmediata. Un proceso interno pide el CAE después del POST. No asumas que el comprobante ya tiene número al crearlo.

Descubrimiento

GET /api público

Devuelve versión y lista de endpoints. No requiere token.

Usuarios

POST /api/usuario público

Registra un usuario y devuelve el api_token. Email duplicado → 409.

CampoObligatorioNotas
nombresí
emailsíformato email válido
passwordsímínimo 6 caracteres
Request
{
  "nombre": "Juan Pérez",
  "email": "juan@empresa.com",
  "password": "minimo6"
}
Response 201
{
  "ok": true,
  "usuario": {
    "id": 1,
    "nombre": "Juan Pérez",
    "email": "juan@empresa.com",
    "api_token": "a1b2c3..."
  }
}
GET /api/usuario/me token

Perfil del usuario autenticado, incluyendo el token actual.

PUT PATCH /api/usuario/me token

Actualiza perfil. Todos los campos son opcionales: nombre, email, password.

POST /api/usuario/me/token token

Regenera el token. El anterior deja de funcionar.

Response
{ "ok": true, "api_token": "nuevo_hex_64..." }

Negocios (emisores)

Requieren token. Solo se listan y modifican los negocios del usuario autenticado.

GET /api/negocios token

Lista negocios. Cada ítem incluye tiene_credenciales y el pto_vta activo.

POST /api/negocios token
CampoObligatorioNotas
nombresí
cuitsí11 dígitos
condicionsícódigo IVA del emisor (ver catálogos)
pto_vtasí1–99999
domiciliono
passphrasenocontraseña de la clave privada
certificadosímultipart: $_FILES['certificado'] (.crt/.cer/.pem). JSON: PEM o base64. Name exacto, no cert.
private_keysímultipart: $_FILES['private_key'] (.key/.pem). JSON: PEM o base64. Name exacto con guion bajo, no privateKey ni key.
Sin el par certificado + private_key el alta responde 400 (no se crea el negocio). En multipart PHP no pone el archivo en $_POST: tiene que ser file part. Preferí /api.php?path=negocios o la URL con barra final para no perder el body en un redirect 301.
Request JSON (PEM)
{
  "nombre": "Mi comercio",
  "cuit": "20123456789",
  "condicion": 1,
  "pto_vta": 1,
  "domicilio": "Av. Corrientes 1000",
  "certificado": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
Response 201
{
  "ok": true,
  "negocio": {
    "id": 12,
    "nombre": "Mi comercio",
    "cuit": "20123456789",
    "condicion": 1,
    "domicilio": "Av. Corrientes 1000",
    "pto_vta": 1,
    "tiene_credenciales": true
  }
}
GET /api/negocios/{id} token

Detalle de un negocio.

PUT PATCH /api/negocios/{id} token

Actualiza datos. Mismos campos que el alta; si enviás pto_vta se actualiza el punto de venta activo.

POST /api/negocios/{id}/credenciales token

Carga certificado y/o clave privada AFIP. JSON o multipart/form-data.

CampoFormato
certificadoarchivo .crt / .cer / .pem, PEM crudo, o base64
certificado_base64alternativa al campo certificado
private_keyarchivo .key / .pem, PEM crudo, o base64
private_key_base64alternativa al campo private_key
passphraseopcional
Sin certificado y clave, POST /api/comprobantes responde error: el negocio no tiene credenciales AFIP cargadas.

Padrón ARCA (constancia de inscripción)

Consulta ws_sr_constancia_inscripcion con el certificado del negocio central (id 1). El token de API alcanza: el negocio del usuario no necesita tener ese web service adherido.

GET POST /api/padron/{cuit} token

También: GET /api/padron?cuit=20386368563 o POST /api/padron con {"cuit":"20386368563"}. Alias: /api/constancia/....

Response 200
{
  "ok": true,
  "padron": {
    "cuit": "20386368563",
    "doc_tipo": 80,
    "razon_social": "PEREZ JUAN",
    "domicilio": "HIDALGO 820 Piso:8 Dpto:E, CIUDAD AUTONOMA BUENOS AIRES, CP 1405",
    "domicilio_detalle": {
      "direccion": "HIDALGO 820 Piso:8 Dpto:E",
      "localidad": "CIUDAD AUTONOMA BUENOS AIRES",
      "provincia": "CIUDAD AUTONOMA BUENOS AIRES",
      "cod_postal": "1405"
    },
    "condicion_iva": 6,
    "condicion_iva_label": "Responsable Monotributo",
    "tipo_persona": "FISICA",
    "estado_clave": "ACTIVO",
    "errores": []
  }
}

condicion_iva usa los mismos códigos que al crear un comprobante (1 RI, 4 Exento, 6 Mono, etc.). Puede ser null si el padrón no permite inferirlo.

Comprobantes

Requieren token. El negocio debe tener un punto de venta activo y credenciales AFIP. El PV se toma automáticamente (el primero activo); no se elige en el POST.

GET /api/comprobantes token
QueryDefaultNotas
negocio_id—filtra por emisor. Ej: ?negocio_id=9 devuelve solo comprobantes de ese negocio. No confundir con GET /api/comprobantes/9 (eso es el detalle del comprobante id 9).
estado—solicitado, procesando, emitido, rechazado, anulado
id_externo—coincidencia exacta con el ID que envió el cliente al crear el comprobante
desde—fecha_cbte ≥ esta fecha. Formato YYYY-MM-DD
hasta—fecha_cbte ≤ esta fecha. Formato YYYY-MM-DD
page1página (1-based)
limit100tamaño de página, 1–100

Ordenados por id descendente. La respuesta incluye paginacion con page, limit, total y pages.

Cada comprobante incluye fecha_cbte (fecha fiscal, YYYY-MM-DD) y fecha_hora_emision (fecha y hora de registro/emisión en America/Argentina/Buenos_Aires, YYYY-MM-DD HH:MM:SS).

Response 200
{
  "ok": true,
  "comprobantes": [
    {
      "id": 99,
      "negocio_id": 9,
      "negocio_nombre": "Mi comercio",
      "estado": "emitido",
      "fecha_cbte": "2026-09-14",
      "fecha_hora_emision": "2026-09-14 12:34:56",
      "created_at": "2026-09-14 12:34:56"
    }
  ],
  "paginacion": {
    "page": 1,
    "limit": 100,
    "total": 250,
    "pages": 3
  }
}
GET /api/comprobantes/{id} token

Detalle + array items. Cuando está emitido incluye cae, cae_vto, numero, pdf_path, xml_path. Si falló: estado: "rechazado" y motivo_rechazo.

POST /api/comprobantes token

Solicita la emisión. Queda en solicitado hasta que el worker autorice en ARCA. Máximo 20 ítems.

CampoObligatorioNotas
negocio_idsí
cbte_tiposíver catálogo; debe coincidir con el IVA del emisor
doc_tiposí80 CUIT o 96 DNI
doc_nrosíCUIT 11 dígitos; DNI 7 u 8
itemsrecomendadoo bien importe_total suelto
receptor_razon_socialsi CUITen DNI: nombre del receptor (alias: receptor_nombre)
condicion_ivasi CUITalias: receptor_condicion_iva; con DNI queda 5
receptor_domicilio_comercialnoalias: receptor_domicilio / receptor_direccion; en DNI es la dirección
receptor_emailno
conceptono1 productos (default), 2 servicios, 3 ambos
condicion_ventanocódigo 1–8 o el nombre; default 1 (Contado)
fecha_cbtenoYYYY-MM-DD (calendario Buenos Aires). Default: hoy AR. Si mandás datetime ISO/UTC, se convierte a fecha en America/Argentina/Buenos_Aires. created_at y updated_at se guardan en esa misma zona.
periodo_desde / periodo_hasta / vto_pagosi concepto 2 o 3YYYY-MM-DD
factura_asociadasi NCnúmero de factura emitida, mismo PV y letra
importe_totalsi no hay itemspara A/B se infiere neto/IVA al 21%

Ítem

CampoObligatorioNotas
descripcionsíalias: producto_servicio
cantidadsí> 0
precio_unitariosí≥ 0
codigono
unidad_medidanodefault UNI; alias: unidad
alicuota_ivanodefault 21
porcentaje_bonificadono
importe_bonificadonosi no viene, se calcula del porcentaje

Factura B a consumidor final (DNI)

Request
{
  "negocio_id": 12,
  "cbte_tipo": 6,
  "doc_tipo": 96,
  "doc_nro": "12345678",
  "receptor_nombre": "Juan Pérez",
  "receptor_direccion": "Av. Siempre Viva 123",
  "condicion_venta": 6,
  "items": [
    {
      "descripcion": "Servicio de consultoría",
      "cantidad": 1,
      "precio_unitario": 10000,
      "alicuota_iva": 21,
      "unidad_medida": "UNI"
    }
  ]
}

Factura A a CUIT

Request
{
  "negocio_id": 12,
  "cbte_tipo": 1,
  "doc_tipo": 80,
  "doc_nro": "30712345678",
  "receptor_razon_social": "Cliente SA",
  "condicion_iva": 1,
  "receptor_domicilio_comercial": "Av. Siempre Viva 123",
  "receptor_email": "facturacion@cliente.com",
  "concepto": 1,
  "condicion_venta": 1,
  "items": [
    {
      "codigo": "SKU-01",
      "descripcion": "Producto",
      "cantidad": 2,
      "precio_unitario": 1500.50,
      "porcentaje_bonificado": 10,
      "alicuota_iva": 21,
      "unidad_medida": "UNI"
    }
  ]
}
Response 201
{
  "ok": true,
  "comprobante": {
    "id": 99,
    "estado": "solicitado",
    "cae": "PENDIENTE",
    "numero": 0,
    "importe_total": 12100,
    "importe_neto": 10000,
    "importe_iva": 2100
  },
  "mensaje": "Solicitud registrada. El comprobante se autorizará en ARCA de forma asíncrona (estado: solicitado)."
}

Con DNI (doc_tipo: 96) la condición IVA queda en Consumidor Final (5). Podés enviar receptor_nombre y receptor_direccion (o los campos receptor_razon_social / receptor_domicilio_comercial); si no hay nombre se usa “Consumidor Final”.

Catálogos

cbte_tipo

CódigoTipoEmisor
1 / 2 / 3Factura / ND / NC AResponsable Inscripto (1)
6 / 7 / 8Factura / ND / NC BResponsable Inscripto (1)
11 / 12 / 13Factura / ND / NC CMonotributo / Exento / similares
19 / 20 / 21Factura / ND / NC Etodos

Notas de crédito (3, 8, 13, 21) exigen factura_asociada (número de factura emitida del mismo punto de venta y letra).

doc_tipo

CódigoDocumento
80CUIT/CUIL (11 dígitos)
96DNI (7 u 8 dígitos)

condicion / condicion_iva

CódigoEtiqueta
1IVA Responsable Inscripto
4IVA Sujeto Exento
5Consumidor Final
6Responsable Monotributo
7Sujeto No Categorizado
8Proveedor del Exterior
9Cliente del Exterior
10IVA Liberado – Ley 19.640
13Monotributista Social
15IVA No Alcanzado

concepto

1 Productos · 2 Servicios · 3 Productos y Servicios.

condicion_venta

1 Contado · 2 Débito · 3 Crédito · 4 Cuenta corriente · 5 Cheque · 6 Transferencia · 7 Otra · 8 Otros electrónicos.

alicuota_iva

0 · 2.5 · 5 · 10.5 · 21 (default) · 27.

estado del comprobante

solicitado · procesando · emitido · rechazado · anulado.

URLs alternativas

Si el rewrite de /api/... no está activo:

Equivalencias
GET  /api.php?path=usuario/me
POST /api.php?path=negocios
POST /api.php?path=negocios/12/credenciales
POST /api.php?path=padron/20386368563
GET  /api.php?path=padron&cuit=20386368563
POST /api.php?path=comprobantes
GET  /api.php?path=comprobantes/99
GET  /api/comprobantes/?id=99
No sigas redirects 301 (http→https o /negocios → /negocios/) con el POST original: el body multipart se pierde. Usá HTTPS desde el inicio y /api.php?path=negocios, o la URL final con barra.

Ejemplos curl

Consultar CUIT (padrón ARCA)
curl -i -H "Authorization: Bearer $TOKEN" \
  "$BASE/api.php?path=padron/20386368563"
Alta de negocio (multipart — recomendado)
curl -i -X POST "$BASE/api.php?path=negocios" \
  -H "Authorization: Bearer $TOKEN" \
  -F "nombre=EVENTO DE PRUEBA NEW" \
  -F "cuit=20386368563" \
  -F "condicion=6" \
  -F "pto_vta=4" \
  -F "domicilio=HIDALGO 820" \
  -F "passphrase=" \
  -F "certificado=@/ruta/certificado.crt;type=application/x-x509-ca-cert" \
  -F "private_key=@/ruta/privada.key;type=application/octet-stream"
Alta de negocio (JSON PEM)
curl -i -X POST "$BASE/api.php?path=negocios" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"nombre\":\"Mi comercio\",\"cuit\":\"20123456789\",\"condicion\":6,\"pto_vta\":4,\"certificado\":\"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\"}"
Listar negocios
TOKEN="..."
BASE="https://emitifacil.com"

curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/negocios"
Listar comprobantes
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/comprobantes?negocio_id=9&desde=2026-09-01&hasta=2026-09-30&page=1&limit=100"
Solicitar comprobante
curl -s -X POST "$BASE/api/comprobantes" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "negocio_id": 12,
    "cbte_tipo": 6,
    "doc_tipo": 96,
    "doc_nro": "12345678",
    "receptor_nombre": "Juan Pérez",
    "receptor_direccion": "Av. Siempre Viva 123",
    "items": [{
      "descripcion": "Venta",
      "cantidad": 1,
      "precio_unitario": 1000,
      "alicuota_iva": 21
    }]
  }'
Consultar estado
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/comprobantes/99"
Fallback sin /api
curl -s -H "X-Api-Token: $TOKEN" "$BASE/api.php?path=usuario/me"