Primer DTE en 5 minutos
Del registro a tu primera factura certificada ante la SAT, paso a paso con código real.
Primer DTE en 5 minutos
Esta guía te lleva desde cero hasta emitir tu primera factura de prueba certificada. Usaremos el entorno test (certificador MOCK) para que no haya riesgo de emitir facturas reales.
En entorno test (qapi_test_...) el certificador es siempre MOCK. No se conecta a la SAT,
no consume cuota y la respuesta es instantánea. Ideal para desarrollo e integración.
Crear tu cuenta
Regístrate en DTEVIA. Este endpoint crea tu cuenta, tu tenant y tu primera API key de test.
curl -X POST https://api.dtevia.com.gt/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "tu@email.com",
"password": "MiPassword123",
"nombre": "Tu Nombre",
"nombre_empresa": "Mi Empresa S.A.",
"nit_emisor": "12345678"
}'const res = await fetch('https://api.dtevia.com.gt/v1/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'tu@email.com',
password: 'MiPassword123',
nombre: 'Tu Nombre',
nombre_empresa: 'Mi Empresa S.A.',
nit_emisor: '12345678',
}),
});
const data = await res.json();
// data.api_key ← ¡guárdalo ahora! Solo se muestra una vez.import requests
res = requests.post('https://api.dtevia.com.gt/v1/auth/register', json={
'email': 'tu@email.com',
'password': 'MiPassword123',
'nombre': 'Tu Nombre',
'nombre_empresa': 'Mi Empresa S.A.',
'nit_emisor': '12345678',
})
data = res.json()
# data['api_key'] ← ¡guárdalo ahora! Solo se muestra una vez.Respuesta (201)
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "tu@email.com", "nombre": "Tu Nombre", "role": "owner" },
"tenant": { "id": "ten_...", "nombre": "Mi Empresa S.A.", "nit_emisor": "12345678", "plan": "starter" },
"api_key": "qapi_test_abc123xyz..."
}El campo api_key solo aparece en esta respuesta. Cópialo y guárdalo en tu .env antes de continuar.
Emitir la factura
Con tu API key, emite una factura de tipo FACT (la única implementada hoy). El campo
precio_unitario va sin IVA — QAPI calcula y valida el IVA automáticamente.
curl -X POST https://api.dtevia.com.gt/v1/invoices \
-H "Authorization: Bearer qapi_test_TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tipo_dte": "FACT",
"moneda": "GTQ",
"fecha_emision": "2026-06-08T10:00:00-06:00",
"emisor": {
"nit": "12345678",
"nombre": "Mi Empresa S.A.",
"nombre_comercial": "Mi Empresa",
"codigo_establecimiento": 1,
"afiliacion_iva": "GEN",
"direccion": {
"calle": "6a Avenida 1-36 Zona 1",
"municipio": "Guatemala",
"departamento": "Guatemala",
"codigo_postal": "01001",
"pais": "GT"
},
"correo": "facturacion@miempresa.com"
},
"receptor": {
"id": "CF",
"tipo_id": "NIT",
"nombre": "Consumidor Final"
},
"frases": [
{ "tipo_frase": 1, "codigo_escenario": 1 }
],
"items": [
{
"tipo": "S",
"cantidad": 1,
"unidad_medida": "UND",
"descripcion": "Servicio de consultoría",
"precio_unitario": 100.00,
"descuento": 0,
"impuestos": [
{
"nombre": "IVA",
"codigo_unidad_gravable": 1,
"monto_gravable": 100.00,
"monto_impuesto": 12.00
}
]
}
]
}'const API_KEY = process.env.QAPI_TEST_KEY!;
const BASE = 'https://api.dtevia.com.gt/v1';
const invoice = {
tipo_dte: 'FACT',
moneda: 'GTQ',
fecha_emision: new Date().toISOString().replace('Z', '-06:00').slice(0, 19) + '-06:00',
emisor: {
nit: '12345678',
nombre: 'Mi Empresa S.A.',
nombre_comercial: 'Mi Empresa',
codigo_establecimiento: 1,
afiliacion_iva: 'GEN',
direccion: {
calle: '6a Avenida 1-36 Zona 1',
municipio: 'Guatemala',
departamento: 'Guatemala',
codigo_postal: '01001',
pais: 'GT',
},
correo: 'facturacion@miempresa.com',
},
receptor: { id: 'CF', tipo_id: 'NIT', nombre: 'Consumidor Final' },
frases: [{ tipo_frase: 1, codigo_escenario: 1 }],
items: [{
tipo: 'S',
cantidad: 1,
unidad_medida: 'UND',
descripcion: 'Servicio de consultoría',
precio_unitario: 100.00,
descuento: 0,
impuestos: [{
nombre: 'IVA',
codigo_unidad_gravable: 1,
monto_gravable: 100.00,
monto_impuesto: 12.00,
}],
}],
};
const res = await fetch(`${BASE}/invoices`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(invoice),
});
const { id, status } = await res.json();
// status === "PROCESSING" — ahora hay que hacer pollingimport axios from 'axios';
const client = axios.create({
baseURL: 'https://api.dtevia.com.gt/v1',
headers: { Authorization: `Bearer ${process.env.QAPI_TEST_KEY}` },
});
const { data } = await client.post('/invoices', {
tipo_dte: 'FACT',
moneda: 'GTQ',
fecha_emision: '2026-06-08T10:00:00-06:00',
emisor: {
nit: '12345678',
nombre: 'Mi Empresa S.A.',
nombre_comercial: 'Mi Empresa',
codigo_establecimiento: 1,
afiliacion_iva: 'GEN',
direccion: {
calle: '6a Avenida 1-36 Zona 1',
municipio: 'Guatemala',
departamento: 'Guatemala',
codigo_postal: '01001',
pais: 'GT',
},
correo: 'facturacion@miempresa.com',
},
receptor: { id: 'CF', tipo_id: 'NIT', nombre: 'Consumidor Final' },
frases: [{ tipo_frase: 1, codigo_escenario: 1 }],
items: [{
tipo: 'S',
cantidad: 1,
unidad_medida: 'UND',
descripcion: 'Servicio de consultoría',
precio_unitario: 100.00,
descuento: 0,
impuestos: [{
nombre: 'IVA',
codigo_unidad_gravable: 1,
monto_gravable: 100.00,
monto_impuesto: 12.00,
}],
}],
});
const { id, status } = data; // status === "PROCESSING"import requests
from datetime import datetime, timezone, timedelta
API_KEY = 'qapi_test_TU_API_KEY'
BASE = 'https://api.dtevia.com.gt/v1'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
payload = {
'tipo_dte': 'FACT',
'moneda': 'GTQ',
'fecha_emision': '2026-06-08T10:00:00-06:00',
'emisor': {
'nit': '12345678',
'nombre': 'Mi Empresa S.A.',
'nombre_comercial': 'Mi Empresa',
'codigo_establecimiento': 1,
'afiliacion_iva': 'GEN',
'direccion': {
'calle': '6a Avenida 1-36 Zona 1',
'municipio': 'Guatemala',
'departamento': 'Guatemala',
'codigo_postal': '01001',
'pais': 'GT',
},
'correo': 'facturacion@miempresa.com',
},
'receptor': {'id': 'CF', 'tipo_id': 'NIT', 'nombre': 'Consumidor Final'},
'frases': [{'tipo_frase': 1, 'codigo_escenario': 1}],
'items': [{
'tipo': 'S',
'cantidad': 1,
'unidad_medida': 'UND',
'descripcion': 'Servicio de consultoría',
'precio_unitario': 100.00,
'descuento': 0,
'impuestos': [{
'nombre': 'IVA',
'codigo_unidad_gravable': 1,
'monto_gravable': 100.00,
'monto_impuesto': 12.00,
}],
}],
}
res = requests.post(f'{BASE}/invoices', json=payload, headers=headers)
data = res.json()
invoice_id = data['id'] # status === "PROCESSING"Respuesta (202)
{
"id": "inv_01jx...",
"status": "PROCESSING",
"tipo_dte": "FACT",
"gran_total": 112.00,
"moneda": "GTQ",
"numero_acceso": "543219876",
"created_at": "2026-06-08T16:00:00.000Z",
"links": { "self": "/v1/invoices/inv_01jx..." }
}¿Por qué 202 y no 200?
La certificación es asíncrona. QAPI acepta la factura, la encola y responde inmediatamente
con PROCESSING. El estado final (CERTIFIED o FAILED) llega en segundos vía polling o webhook.
Consultar el estado (polling)
Usa el id de la respuesta anterior para consultar el estado hasta que sea CERTIFIED.
curl https://api.dtevia.com.gt/v1/invoices/inv_01jx... \
-H "Authorization: Bearer qapi_test_TU_API_KEY"async function waitForCertification(invoiceId: string, apiKey: string) {
const BASE = 'https://api.dtevia.com.gt/v1';
const headers = { Authorization: `Bearer ${apiKey}` };
for (let i = 0; i < 10; i++) {
const res = await fetch(`${BASE}/invoices/${invoiceId}`, { headers });
const invoice = await res.json();
if (invoice.status === 'CERTIFIED') return invoice;
if (invoice.status === 'FAILED') throw new Error(invoice.error_message);
await new Promise(r => setTimeout(r, 1000)); // espera 1 segundo
}
throw new Error('Timeout esperando certificación');
}
const certified = await waitForCertification(id, API_KEY);
console.log('UUID SAT:', certified.uuid_sat);
console.log('Número DTE:', certified.numero_dte);
console.log('PDF:', certified.pdf_url);import time
def wait_for_certification(invoice_id, api_key, max_attempts=10):
url = f'{BASE}/invoices/{invoice_id}'
headers = {'Authorization': f'Bearer {api_key}'}
for _ in range(max_attempts):
res = requests.get(url, headers=headers)
invoice = res.json()
if invoice['status'] == 'CERTIFIED':
return invoice
if invoice['status'] == 'FAILED':
raise Exception(invoice['error_message'])
time.sleep(1)
raise TimeoutError('Tiempo de espera agotado')
certified = wait_for_certification(invoice_id, API_KEY)
print('UUID SAT:', certified['uuid_sat'])
print('Número DTE:', certified['numero_dte'])
print('PDF:', certified['pdf_url'])Respuesta certificada
{
"id": "inv_01jx...",
"status": "CERTIFIED",
"tipo_dte": "FACT",
"gran_total": 112.00,
"moneda": "GTQ",
"numero_acceso": 543219876,
"certificador": "MOCK",
"uuid_sat": "A1B2C3D4-E5F6-A7B8-C9D0-E1F2A3B4C5D6",
"serie": "QAPI",
"numero_dte": 1,
"pdf_url": "https://report.feel.com.gt/ingfacereport/ingfacereport_documento?uuid=A1B2C3D4-...",
"certified_at": "2026-06-08T16:00:02.000Z"
}¡Listo!
Tu primera factura está certificada. En entorno test el certificador es MOCK, así que:
uuid_sates un UUID generado localmente (no es un UUID real de la SAT)certificadoraparece como"MOCK"- El
pdf_urlno resuelve (el UUID no existe ante la SAT); la vista de prueba está en el dashboard, en el detalle de la factura (botón "Ver factura")
Para emitir facturas reales, necesitas cambiar a entorno live — consulta la guía de Entornos.
¿Qué sigue?
- Entornos — diferencias entre test y live, y cómo activar el certificador real
- Emitir Factura — guía completa con todos los campos y casos de uso
- Webhooks — recibe notificaciones en vez de hacer polling
- Manejo de Errores — qué hacer cuando algo falla