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 solicitud espera a la DIAN simulada y responde 201 con el documento en accepted: trae su number (SBX1, SBX2…) y su cufe (ver códigos de validación). Copia el valor de id.
Si la respuesta es 202, el documento sigue en proceso (pending o sending): consúltalo en el paso 4. Los códigos de respuesta están en emitir una factura.
4. Consulta el documento
Sección titulada «4. Consulta el documento»curl -s -H "Authorization: Bearer $FOLIIOS_API_KEY" \ "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents/ID_DEL_DOCUMENTO"El GET devuelve el mismo documento en su estado actual. Después de un 202, repítelo cada par de segundos hasta que el status sea accepted.
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"]) # 201 ... accepted
# Solo hace falta después de un 202: el documento sigue en proceso. for _ in range(15): if documento["status"] in ("accepted", "rejected", "failed"): break time.sleep(2) _, documento, _ = llamar("GET", f"/v1/companies/{EMPRESA}/documents/{documento['id']}") 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.