Ir al contenido

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»
  1. 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.
  2. Anota el identificador de la empresa de prueba. Empieza por cmp_.
  3. Guárdalos como variables de entorno (o en un archivo .env que no esté en el control de versiones):
Ventana de terminal
export FOLIIOS_API_KEY="pega-aquí-tu-clave-sk_sandbox"
export FOLIIOS_COMPANY_ID="cmp_..."

El catálogo de escenarios del sandbox es público, así que no necesita clave:

Ventana de terminal
curl -s https://api.foliios.com/v1/sandbox/scenarios | head -c 300

Si responde JSON, llegas a la API. Con tu clave, la lectura de la verificación de integración debe responder 200:

Ventana de terminal
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"

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.

Ventana de terminal
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.

Ventana de terminal
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.

Solo biblioteca estándar. Guarda este archivo como foliios_inicio.py; las demás guías reutilizan la función llamar.

import json
import os
import time
import urllib.error
import urllib.request
import 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"))
  • 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-check te dice qué has probado y qué falta: factura aceptada, notas crédito y débito, contingencia y webhook. Con complete: true terminaste. Ver verificación de integración.