Skip to content
New VERI*FACTU arrives in 2027, and elpizzero is ready for it. Find out how Chat with usPartners: para agencias
API Export Read-only · api@elpizzero.com

API Export

Read-only REST API to export your elpizzero account data —orders, menu, zones, restaurant and metrics— to your own systems (CRM, BI, accounting, displays, etc.). It includes a live stream (SSE) and outgoing webhooks so you can react to changes in real time.

Scope. This API does not create or modify orders. Order ingestion into elpizzero is not public and is not documented here: it is enabled per account, after validation. If you need something like that, write to us at api@elpizzero.com.

Authentication

Bearer token in the header. Tokens have the format elp_live_<32hex> or elp_test_<32hex>.

Authorization: Bearer elp_live_3b8f0c2a1d4e5f6079a8b7c6d5e4f3a2

Generate and revoke tokens in Dashboard → Integrations → API Keys, choosing their permissions. The full token is shown only once.

Permissions

ScopeEnables
pedidos.leer/pedidos, /pedidos/{id}, /pedidos/{id}/historial, /resumen
carta.leer/carta
restaurante.leer/restaurante, /zonas
stream.escuchar/stream (SSE)
webhook.recibir/webhooks (create/delete/list)

Token without the required scope → 403 alcance_insuficiente.

Environments

elp_live_ and elp_test_ read the same real data; there is no sandbox with simulated data. The prefix is just a label to help you tell your tokens apart.

Rate limit

60 req/min per token. Headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. When exceeded → 429 + Retry-After.

Conventions

Errors

Uniform body + consistent HTTP status code:

{
  "ok": false,
  "error": "Esta llave no tiene el alcance necesario: pedidos.leer",
  "codigo": "alcance_insuficiente",
  "doc": "https://elpizzero.com/docs/api/export#auth"
}
HTTPcodigo
401llave_ausente · llave_invalida · llave_revocada · llave_expirada
403alcance_insuficiente · restaurante_inactivo
400parametro_invalido (includes valores_validos)
404no_encontrado
405 / 429metodo_invalido · limite_alcanzado

List orders

GET /pedidos

The restaurant's orders, ordered by id descending, cursor pagination.

QueryTipoDescription
estadocsvpendienteconfirmadoen_preparacionlistoen_repartoentregadocancelado
tipoenumrepartorecogidamesa
desde / hastaISORange on creado_en (desde ≤ x < hasta).
limiteint1–100, default 25.
cursorstringValue of paginacion.siguiente from the previous page.
curl "https://elpizzero.com/api/export/pedidos?estado=entregado&desde=2026-05-01&limite=50" \
  -H "Authorization: Bearer elp_live_..."
{
  "datos": [ /* Objeto Pedido */ ],
  "paginacion": { "limite":50, "siguiente":"ni", "anterior":null, "total_aprox":494 }
}
Iterate with cursor = paginacion.siguiente until it is null to dump the entire history.

Get an order

GET /pedidos/{id}

Returns the Order object. {id} with prefix, e.g. ped_ni. 404 no_encontrado if it is not yours.

Order history

GET /pedidos/{id}/historial
{
  "pedido_id": "ped_ni",
  "eventos": [
    { "momento":"2026-05-31T20:42:15Z", "accion":"order_created",
      "origen":"sistema", // sistema|panel|tpv|apk_recepcion|api
      "usuario":null, "detalle":{} }
  ]
}

Menu

GET /carta

Full tree: categories → products → sizes → option groups → options. ?solo_disponibles=true excludes hidden/sold-out items.

{
  "restaurante_id":"rest_1", "restaurante_slug":"tu-restaurante",
  "moneda":"EUR", "idioma_origen":"es", "generado_en":"2026-06-01T18:41:27Z",
  "categorias": [{
    "id":"cat_v", "nombre":"Pizzas", "activa":true,
    "visible_dias":["lun","mar","mie","jue","vie","sab","dom"], "visible_horario":null,
    "productos": [{
      "id":"prod_3J", "nombre":"Margarita", "precio_cents":680,
      "disponible":true, "destacado":false, "alergenos":["gluten","lacteos"],
      "tamaños":[{ "id":"tam_11", "nombre":"mediana", "precio_cents":680, "predeterminado":true }],
      "grupos_opcion":[{ "id":"grp_73", "nombre":"Extras", "requerido":false,
        "min_seleccion":0, "max_seleccion":5,
        "opciones":[{ "id":"opt_8", "nombre":"Extra queso", "precio_cents":120 }] }]
    }]
  }]
}

Delivery zones

GET /zonas

Zones with polygon in GeoJSON (coordinates [lng, lat], closed ring).

{ "datos": [{
  "id":"zon_8", "nombre":"Albatera", "slug":"albatera",
  "color":"#44c3e5", "tarifa_cents":250, "pedido_minimo_cents":2000,
  "minutos_estimados":40, "activa":true,
  "poligono":{ "tipo":"Polygon", "coordenadas":[[[-0.872,38.186], /* … */]] }
}] }

Restaurant

GET /restaurante

Restaurant profile: contact, location, hours per day, branding, languages per channel, reputation. No sensitive tax data.

{
  "id":"rest_1", "slug":"tu-restaurante", "nombre":"Tu Restaurante",
  "moneda":"EUR", "zona_horaria":"Europe/Madrid",
  "acepta_pedidos":true, "pedido_minimo_cents":500,
  "horario":{ "viernes":[{"desde":"18:45","hasta":"23:30"}] /* … */ },
  "contacto":{ "email":"…", "telefono":"…", "web":"…" },
  "ubicacion":{ "direccion":"…", "ciudad":"…", "lat":38.14, "lng":-0.88 },
  "branding":{ "logo_url":"…", "color_primario":"…", "galeria":[] },
  "reputacion":{ "puntuacion":4.7, "votos":128 }
}

Summary / metrics

GET /resumen?periodo=30d

periodo ∈ hoyayer7d30dmes_actual. Calculated in the restaurant's time zone; excludes cancelled/rejected orders.

{
  "periodo":"30d", "desde":"2026-05-03T00:00:00+02:00", "hasta":"2026-06-02T00:00:00+02:00",
  "zona_horaria":"Europe/Madrid",
  "kpis":{ "pedidos_total":442, "pedidos_cancelados":52, "clientes_distintos":413,
    "ventas_cents":893435, "ticket_medio_cents":2021, "moneda":"EUR" },
  "mix_tipo":{ "reparto":268, "recogida":171, "mesa":3 },
  "top_productos":[{ "producto_id":"prod_3K", "nombre":"Margarita", "unidades":150, "ventas_cents":113845 }]
}

Live stream (SSE)

GET /stream

Server-Sent Events. Pushes events instantly. Auth via query (EventSource does not support headers). Native reconnection every 5 min via Last-Event-ID; conexion.ping every ~25 s.

const es = new EventSource("https://elpizzero.com/api/export/stream?llave=elp_live_...");
es.addEventListener("pedido.nuevo", e => {
  const ev = JSON.parse(e.data);   // ev.datos.pedido = Objeto Pedido
  console.log(ev.datos.pedido.numero);
});

Optional filter ?eventos=pedido.nuevo,pedido.aceptado. Event types emitted:

pedido.nuevopedido.aceptadopedido.rechazadopedido.estado_cambiadopedido.item_modificadopedido.entregadopedido.cancelado

Outgoing webhooks

Pull → push alternative: we send a POST to your URL when an order changes. Outbound only.

POST /webhooks
curl -X POST "https://elpizzero.com/api/export/webhooks" \
  -H "Authorization: Bearer elp_live_..." -H "Content-Type: application/json" \
  -d '{"url":"https://tu-sistema.com/hook","nombre":"Mi CRM","eventos":["pedido.aceptado","pedido.entregado"]}'
{ "id":"wh_3", "url":"https://tu-sistema.com/hook",
  "eventos":["pedido.aceptado","pedido.entregado"],
  "secreto_firma":"a1b2…(64 hex, se muestra UNA vez)", "activo":true }

Events: those of the stream + wildcards pedido.* and *.  GET /webhooks lists · DEL /webhooks/{id} deletes.

Delivery and signature

Each delivery includes these headers; the body is the same object as in the stream:

X-Elp-Evento: pedido.aceptado
X-Elp-Id: 84213            # úsalo como clave de idempotencia
X-Elp-Firma: <hmac_sha256_hex(secreto, cuerpo_crudo)>
X-Elp-Intento: 1
// Verificación (Node)
const firma = crypto.createHmac("sha256", SECRETO).update(rawBody).digest("hex");
if (firma !== req.headers["x-elp-firma"]) return res.sendStatus(401);
We retry on failure (X-Elp-Intento increments). Deduplicate by X-Elp-Id.

Order object

Structure returned by /pedidos, /pedidos/{id}, the stream and webhooks. Customer data anonymized in the example. direccion/mesa/entrega/fiscal are null when not applicable.

{
  "id":"ped_ni", "numero":"20260531-095",
  "tipo":"reparto",          // reparto|recogida|mesa
  "estado":"en_preparacion",
  "fuente":"web_publica",    // web_publica|tpv|manual|admin
  "creado_en":"2026-05-31T20:42:15Z", "aceptado_en":"…Z",
  "entregado_en":null, "programado_para":null,

  "restaurante":{ "id":"rest_1", "slug":"tu-restaurante", "nombre":"Tu Restaurante" },
  "cliente":{ "nombre":"Juan Pérez", "telefono":"+34 600 000 000",
    "email":"cliente@ejemplo.com", "consintio_marketing":false },
  "direccion":{ "linea":"Calle Mayor 22", "codigo_postal":null, "pais":"ES", "lat":38.154, "lng":-0.880 },
  "mesa":null,

  "items":[{ "id":"itm_A3", "producto_id":"prod_3K", "nombre":"Margarita",
    "tamaño":"mediana", "cantidad":1,
    "precio_unitario_cents":720, "opciones_total_cents":120, "total_cents":840,
    "estado_cocina":"pendiente",
    "opciones":[{ "id":"opc_5", "grupo":"Extras", "nombre":"Extra queso", "cantidad":1, "precio_cents":120 }] }],

  "totales":{ "moneda":"EUR", "subtotal_cents":840, "descuento_cents":0, "propina_cents":0,
    "tarifa_reparto_cents":100, "impuestos_cents":85, "total_cents":1025,
    "iva_desglose":[{ "tipo_porcentaje":10, "base_cents":940, "iva_cents":85 }] },
  "pago":{ "metodo":"online", "estado":"pagado", "proveedor":"stripe",
    "referencia_externa":"pi_3Td…", "pagos":[] },
  "entrega":{ "repartidor_nombre":null, "zona_id":"zon_a", "zona_nombre":"Granja de Rocamora", "tarifa_cents":100 },
  "fiscal":{ "factura_numero":"FA-1132/2026", "factura_fecha":"…Z", "factura_pdf":"https://…/factura.pdf" },
  "instrucciones":null
}

Example: kitchen TV display

Self-contained HTML+JS (read-only): paste your token, open it on a TV and orders arrive live through the stream.

→ Open kitchen TV example