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 respuesta es 202 con el documento en pending. Copia el valor de id.

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

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"])
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"))
  • 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.