Buenas practicas

Recomendaciones para integrar de forma estable: reintentos seguros, control de duplicados y buen manejo del estado fiscal.

Idempotencia#

Envia siempre Idempotency-Key en la emision: una cadena ASCII estable por intencion (por ejemplo FACTURA-2026-0001).

curl -s -X POST \
  "https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes" \
  -H "X-Api-Key: $ECUAFACT_API_KEY" \
  -H "Idempotency-Key: FACTURA-2026-0001" \
  -H "Content-Type: application/json" \
  -d @comprobante.json
using System.Text;

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-Api-Key", apiKey);
http.DefaultRequestHeaders.Add("Idempotency-Key", "FACTURA-2026-0001");

string json = await File.ReadAllTextAsync("comprobante.json");
using var contenido = new StringContent(json, Encoding.UTF8, "application/json");
using var respuesta = await http.PostAsync(
    "https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes",
    contenido);

Console.WriteLine($"{(int)respuesta.StatusCode} {await respuesta.Content.ReadAsStringAsync()}");
import json, os, requests

headers = {
    "X-Api-Key": os.environ["ECUAFACT_API_KEY"],
    "Idempotency-Key": "FACTURA-2026-0001",
}
with open("comprobante.json", encoding="utf-8") as archivo:
    cuerpo = json.load(archivo)

r = requests.post(
    "https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes",
    headers=headers, json=cuerpo, timeout=30)
print(r.status_code, r.text)
import { readFile } from 'node:fs/promises';

const cuerpo = await readFile('comprobante.json', 'utf8');
const res = await fetch(
  'https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes',
  {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.ECUAFACT_API_KEY,
      'Idempotency-Key': 'FACTURA-2026-0001',
      'Content-Type': 'application/json'
    },
    body: cuerpo
  });
console.log(res.status, await res.text());
<?php
$ch = curl_init('https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-Api-Key: ' . getenv('ECUAFACT_API_KEY'),
        'Idempotency-Key: FACTURA-2026-0001',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => file_get_contents('comprobante.json'),
]);
$respuesta = curl_exec($ch);
$estado = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $estado . ' ' . $respuesta;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;

HttpClient cliente = HttpClient.newHttpClient();
String cuerpo = Files.readString(Path.of("comprobante.json"));

HttpRequest peticion = HttpRequest.newBuilder()
    .uri(URI.create("https://staging-api.mynexusapi.com/v1/contribuyentes/0123456789/comprobantes"))
    .header("X-Api-Key", System.getenv("ECUAFACT_API_KEY"))
    .header("Idempotency-Key", "FACTURA-2026-0001")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(cuerpo))
    .build();

HttpResponse<String> respuesta = cliente.send(peticion, HttpResponse.BodyHandlers.ofString());
System.out.println(respuesta.statusCode() + " " + respuesta.body());
using Ecuafact.Sdk;

// client: cliente EcuafactClient - ver SDK .NET
EmisionResultado resultado = await client.EmitirAsync(comprobante);

// Si el proceso reintenta, reenvia la MISMA clave.
string clave = resultado.IdempotencyKey;
from ecuafact import EcuafactClient

# client: cliente EcuafactClient - ver SDK Python
resultado = client.emitir(comprobante)

# Si el proceso reintenta, reenvia la MISMA clave.
clave = resultado.idempotency_key
import { EcuafactClient } from "@ecuafact/sdk";

// client: cliente EcuafactClient - ver SDK Node.js
const resultado = await client.emitir(comprobante);

// Si el proceso reintenta, reenvia la MISMA clave.
const clave = resultado.idempotencyKey;
<?php
use Ecuafact\Sdk\EcuafactClient;

// $client: cliente EcuafactClient - ver SDK PHP
$resultado = $client->emitir($comprobante);

// Si el proceso reintenta, reenvia la MISMA clave.
$clave = $resultado->idempotencyKey;
import com.ecuafact.sdk.EcuafactClient;

// client: cliente EcuafactClient - ver SDK Java
EmisionResultado resultado = client.emitir(comprobante);

// Si el proceso reintenta, reenvia la MISMA clave.
String clave = resultado.idempotencyKey();
  • Misma clave y mismo contenido: 202 con codigo 201 y la misma operacion; no crea un comprobante nuevo.
  • Misma clave con otro contenido: 409 (codigo 403).
  • Guarda la Idempotency-Key y la referenciaExterna que enviaste. Si tu proceso reintenta por un timeout, reenvia las mismas para no crear un comprobante duplicado.

Errores y reintentos#

Situacion Accion
429 (limite o cupo), 502, 503, 504 Reintentar con espera exponencial (con jitter) y respetar Retry-After
400, 401, 403, 404, 409, 422 No reintentar sin corregir la causa
Timeout de red Reintentar con la misma Idempotency-Key

Los limites de solicitudes y el manejo del 429 se explican en Limite de request.

  • Registra el X-Correlation-Id de cada respuesta: identifica la peticion ante soporte.
  • No reintentes la emision con una referencia nueva: es lo que duplica documentos.

Emision en lote y concurrencia#

  • Un comprobante por intencion: no reutilices el mismo numero (estab/ptoEmi/secuencial) para otra emision.
  • Conserva fechas e importes tal como los originaste; no los reescribas en reintentos.
  • No envies campos fuera de la Estructura del comprobante: los campos desconocidos se rechazan.
  • Puedes emitir en paralelo con la misma API Key; manten un numero de solicitudes moderado y respeta 429.

Seguimiento del estado#

  • La emision responde 202: la solicitud quedo recibida, no autorizada. No esperes el resultado fiscal en la misma respuesta.
  • Consulta GET /v1/operaciones/{id} o espera el webhook.
  • Evita el sondeo agresivo: usa el webhook como fuente principal y la consulta como respaldo.

Webhooks#

  • Verifica siempre la firma antes de interpretar el cuerpo (Verificacion de firma).
  • Responde 2xx rapido y procesa en segundo plano.
  • Trata los eventos como idempotentes: un mismo resourceId puede reentregarse.

Cupo#

  • El cupo se descuenta al resolverse la operacion, sea autorizada o rechazada.
  • Revisa el saldo en /v1/consumo antes de lotes grandes.
  • Sin cupo, la emision responde 429 (codigo 501); no insistas en bucle.

Formatos#

  • Importes: numeros JSON decimales con punto, sin comillas.
  • Fechas fiscales: dd/MM/yyyy. Periodo: MM/yyyy.
  • Codigos e identificaciones: cadenas.

Seguridad#

  • Envia X-Api-Key solo desde tu backend; nunca la expongas en el navegador ni en aplicaciones cliente.
  • Manten autorizadas solo las IP de origen que usan la API.
  • No compartas tu API Key entre integraciones distintas.

Checklist antes de produccion#

  • Idempotency-Key estable y reutilizada en los reintentos.
  • Reintentos con espera exponencial para 429/502/503/504.
  • Numero de comprobante unico por emision.
  • Webhook con verificacion de firma e idempotente.
  • Guarda el X-Correlation-Id para reportar a soporte.
  • Monitoreo del saldo en /v1/consumo.

Siguientes pasos#

No se pudo completar la operacion. Recargar ✕