Inicio rápido
Vas a emitir una factura en el sandbox: un ambiente simulado, sin efecto fiscal, que nunca llama a la DIAN ni envía correos reales. Necesitas una cuenta en el portal de foliios, curl y, si quieres el ejemplo en Python, Python 3.
1. Consigue tu clave y el identificador de tu empresa
Sección titulada «1. Consigue tu clave y el identificador de tu empresa»- Entra al portal y elige el ambiente Sandbox. La primera vez, foliios deja todo listo: crea una empresa de prueba y una clave que empieza por
sk_sandbox_. La clave se muestra una sola vez: guárdala. - Anota el identificador de la empresa de prueba. Empieza por
cmp_. - Guárdalos como variables de entorno (o en un archivo
.envque no esté en el control de versiones):
export FOLIIOS_API_KEY="pega-aquí-tu-clave-sk_sandbox"export FOLIIOS_COMPANY_ID="cmp_..."2. Comprueba la conexión
Sección titulada «2. Comprueba la conexión»El catálogo de escenarios del sandbox es público, así que no necesita clave:
curl -s https://api.foliios.com/v1/sandbox/scenarios | head -c 300Si responde JSON, llegas a la API. Con tu clave, la lectura de la verificación de integración debe responder 200:
curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer $FOLIIOS_API_KEY" \ "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/sandbox/integration-check"3. Emite una factura
Sección titulada «3. Emite una factura»La resolución del sandbox es siempre 18760000000000 (prefijo SBX, numeración automática). Cada emisión exige una cabecera Idempotency-Key, un valor único por venta que permite reintentar sin duplicar (más aquí). Los montos y cantidades van como texto, no como números JSON.
curl -s -X POST "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents" \ -H "Authorization: Bearer $FOLIIOS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: venta-0001" \ -d '{ "resolution_id": "18760000000000", "type": "invoice", "customer": { "identification": {"type": "nit", "number": "900373076"}, "name": "Comercializadora Andina S.A.S.", "emails": ["compras@andina.example"] }, "lines": [{ "code": "PARQ", "description": "Parqueadero por horas", "quantity": "3", "unit": "HUR", "unit_price": "4000.00", "taxes": [{"scheme": "iva", "percent": "19"}] }] }'La respuesta es 202 con el documento en pending. Copia el valor de id.
4. Mira cómo termina
Sección titulada «4. Mira cómo termina»curl -s -H "Authorization: Bearer $FOLIIOS_API_KEY" \ "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents/ID_DEL_DOCUMENTO"En unos segundos el status pasa de pending a accepted, con su number (SBX1, SBX2…) y su cufe (ver códigos de validación). Si lo consultas muy pronto verás pending o sending: vuelve a pedirlo.
El mismo flujo en Python
Sección titulada «El mismo flujo en Python»Solo biblioteca estándar. Guarda este archivo como foliios_inicio.py; las demás guías reutilizan la función llamar.
import jsonimport osimport timeimport urllib.errorimport urllib.requestimport uuid
API = "https://api.foliios.com"EMPRESA = os.environ["FOLIIOS_COMPANY_ID"]
def llamar(metodo, ruta, cuerpo=None, cabeceras=None): """Devuelve (estado HTTP, JSON, cabeceras). La clave sale del entorno.""" pedido = urllib.request.Request( API + ruta, method=metodo, data=None if cuerpo is None else json.dumps(cuerpo).encode(), headers={ "Authorization": "Bearer " + os.environ["FOLIIOS_API_KEY"], "Content-Type": "application/json", **(cabeceras or {}), }, ) try: with urllib.request.urlopen(pedido) as respuesta: return respuesta.status, json.load(respuesta), respuesta.headers except urllib.error.HTTPError as error: return error.code, json.load(error), error.headers
if __name__ == "__main__": factura = { "resolution_id": "18760000000000", "type": "invoice", "customer": { "identification": {"type": "nit", "number": "900373076"}, "name": "Comercializadora Andina S.A.S.", "emails": ["compras@andina.example"], }, "lines": [ { "code": "PARQ", "description": "Parqueadero por horas", "quantity": "3", "unit": "HUR", "unit_price": "4000.00", "taxes": [{"scheme": "iva", "percent": "19"}], } ], } estado, documento, _ = llamar( "POST", f"/v1/companies/{EMPRESA}/documents", factura, {"Idempotency-Key": "venta-" + str(uuid.uuid4())}, ) print(estado, documento["id"], documento["status"])
for _ in range(15): _, documento, _ = llamar("GET", f"/v1/companies/{EMPRESA}/documents/{documento['id']}") if documento["status"] in ("accepted", "rejected", "failed"): break time.sleep(2) print(documento["status"], documento.get("number"), documento.get("cufe"))5. Mira el correo y comprueba tu avance
Sección titulada «5. Mira el correo y comprueba tu avance»- Buzón de pruebas. En el sandbox no sale ningún correo: el que recibiría el comprador (con el XML y el PDF) queda en el buzón de pruebas del portal.
- Verificación de integración.
GET /v1/companies/{company_id}/sandbox/integration-checkte dice qué has probado y qué falta: factura aceptada, notas crédito y débito, contingencia y webhook. Concomplete: trueterminaste. Ver verificación de integración.
Qué sigue
Sección titulada «Qué sigue»- Emitir una factura, también a consumidor final.
- Notas crédito y débito.
- Rechazos y reenvío: provócalos a propósito con los escenarios del sandbox.
- Webhooks y verificar la firma.
- ¿Prefieres que lo haga un agente de código? Usa el manual del agente.