API v1.0

API Reference

API REST pública de ForkCast. Integrá tu sistema con órdenes de compra, sincronizá datos con webhooks, y automatizá flujos de aprobación.

Introducción

La API de ForkCast permite a restaurantes y proveedores acceder a sus datos de forma programática. Podés consultar órdenes de compra, crear nuevas desde tu propio sistema, y recibir notificaciones en tiempo real via webhooks.

Todos los endpoints retornan JSON y usan autenticación por API key.

Todas las requests deben usar HTTPS

Body en JSON, respuestas en JSON

Fechas en formato ISO 8601 (UTC)

Request de ejemplo

curl https://app.forkcast.tech/api/v1/purchase-orders \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Response

{
  "data": [
    {
      "id": 42,
      "status": "pending",
      "date": "2026-05-20T10:00:00Z",
      "items": [...]
    }
  ],
  "meta": { "timestamp": "..." }
}

Autenticación

La API usa autenticación por API key. Incluí tu key en cada request usando el header X-API-Key.

Las API keys se generan desde Configuración → API & Desarrolladores dentro de la app. Cada key tiene un scope (read o write) y puede tener fecha de expiración.

Formato de key

Las keys tienen el formato sk_live_ seguido de caracteres aleatorios. Guardá tu key de forma segura — no se puede recuperar después de generarla.

Header requerido

X-API-Key: sk_live_xxxxxxxxxxxx

401 Unauthorized — key ausente, inválida o revocada.

Con curl

curl https://app.forkcast.tech/api/v1/purchase-orders \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json"

Scopes

Cada API key tiene un scope que define qué puede hacer. Usá el scope mínimo necesario para tu integración.

read Solo lectura

Puede consultar órdenes de compra y webhooks. Ideal para integraciones de reporting y dashboards externos.

write Lectura y escritura

Incluye todo lo de read más crear/actualizar órdenes y gestionar webhooks. Para integraciones ERP completas.

Permisos por endpoint

Endpoint read write
GET /purchase-orders
GET /purchase-orders/:id
POST /purchase-orders
PUT /purchase-orders/:id
PUT /purchase-orders/:id/status
POST /purchase-orders/bulk
GET /webhooks
POST /webhooks
DELETE /webhooks/:id

Base URL

Todos los endpoints de la API v1 usan la siguiente base URL. No hay versión en cada endpoint individual.

Base URL

https://app.forkcast.tech/api/v1

Ejemplo completo:

https://app.forkcast.tech/api/v1/purchase-orders

Órdenes de compra

GET /purchase-orders

Retorna las órdenes de compra asociadas al owner del API key. Los resultados están ordenados por fecha descendente.

Proveedor→ sus propias órdenes recibidas
Restaurante→ sus propias órdenes emitidas
Master→ todas las órdenes de sus franquicias (via price lists del master)

Query params opcionales

ParamTipoDescripción
statusstringFiltrar: pending, approved, rejected, delivered
fromdateFecha desde (ISO 8601)
todateFecha hasta (ISO 8601)

Respuestas

200Lista de órdenes
401API key inválida
curl "https://app.forkcast.tech/api/v1/purchase-orders?status=pending" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"
const res = await fetch(
  'https://app.forkcast.tech/api/v1/purchase-orders?status=pending',
  { headers: { 'X-API-Key': process.env.FORKCAST_API_KEY } }
);
const { data } = await res.json();
import requests

res = requests.get(
    "https://app.forkcast.tech/api/v1/purchase-orders",
    params={"status": "pending"},
    headers={"X-API-Key": FORKCAST_API_KEY},
)
orders = res.json()["data"]

Response 200

{
  "data": [
    {
      "id": 42,
      "restaurantId": 1,
      "supplierId": 5,
      "status": "pending",
      "date": "2026-05-20T00:00:00Z",
      "items": [
        {
          "name": "Harina 000",
          "quantity": 10,
          "unit": "kg",
          "price": 850.00
        }
      ],
      "isBillable": false,
      "createdAt": "2026-05-20T10:00:00Z"
    }
  ],
  "meta": {
    "timestamp": "2026-05-20T12:00:00Z"
  }
}
GET /purchase-orders/{id}

Obtiene el detalle completo de una orden de compra por ID. Solo podés acceder a órdenes que pertenecen al owner de tu API key.

Path params

ParamTipoDescripción
idintegerID de la orden

Respuestas

200Detalle de la orden
403Sin acceso a esta orden
404Orden no encontrada
curl https://app.forkcast.tech/api/v1/purchase-orders/42 \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"
const res = await fetch(
  `https://app.forkcast.tech/api/v1/purchase-orders/${id}`,
  { headers: { 'X-API-Key': process.env.FORKCAST_API_KEY } }
);
const { data } = await res.json();
res = requests.get(
    f"https://app.forkcast.tech/api/v1/purchase-orders/{order_id}",
    headers={"X-API-Key": FORKCAST_API_KEY},
)
order = res.json()["data"]
POST /purchase-orders

Requiere scope write

Crea una nueva orden de compra en nombre del owner del API key. Si la key pertenece a un restaurante, la orden se crea como restaurante; si pertenece a un proveedor, como proveedor.

Body (JSON)

CampoTipoReq.Descripción
itemsarrayProductos de la orden
supplierIdintegerID del proveedor
priceListIdintegerID de lista de precios
franchiseIdintegerID de franquicia (solo masters)
dateISO 8601Fecha (default: ahora)
statusstringEstado inicial (default: pending)

items[]

CampoTipoReq.
namestring
quantitynumber
unitstring
pricenumber
packageSizenumber
unitsPerPackagenumber
packagingNamestring
normalizedNamestring

Respuestas

201Orden creada
400Datos inválidos
403Scope write requerido
curl https://app.forkcast.tech/api/v1/purchase-orders \
  -X POST \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "supplierId": 5,
    "items": [
      {
        "name": "Harina 000",
        "quantity": 10,
        "unit": "kg",
        "price": 850.00
      }
    ]
  }'
const res = await fetch(
  'https://app.forkcast.tech/api/v1/purchase-orders',
  {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.FORKCAST_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      supplierId: 5,
      items: [
        { name: 'Harina 000', quantity: 10, unit: 'kg', price: 850 }
      ],
    }),
  }
);
const { data } = await res.json();
res = requests.post(
    "https://app.forkcast.tech/api/v1/purchase-orders",
    json={
        "supplierId": 5,
        "items": [
            {"name": "Harina 000",
             "quantity": 10,
             "unit": "kg",
             "price": 850.0}
        ],
    },
    headers={"X-API-Key": FORKCAST_API_KEY},
)
order = res.json()["data"]

Response 201

{
  "data": {
    "id": 43,
    "status": "pending",
    "date": "2026-05-20T12:00:00Z",
    "items": [...],
    "isBillable": false
  }
}

Response 422 · ORDER_OUT_OF_WINDOW

{
  "code": "ORDER_OUT_OF_WINDOW",
  "message": "Este proveedor solo recibe pedidos los días: 1, 2.",
  "supplierId": 5,
  "allowedDays": [1, 2],
  "nextAllowedDate": "2026-06-22"
}

Se dispara cuando el proveedor (o su lista de precios) tiene orderWindowDays configurado y hoy no entra. Ver Ventana de recepción.

PUT /purchase-orders/{id}/status

Requiere scope write

Actualiza el estado de una orden de compra. Solo podés modificar órdenes del owner de tu API key.

Estados válidos

pending approved rejected delivered

Respuestas

200Estado actualizado
400Estado inválido
403Sin acceso
404Orden no encontrada
curl https://app.forkcast.tech/api/v1/purchase-orders/42/status \
  -X PUT \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "status": "delivered" }'
await fetch(
  `https://app.forkcast.tech/api/v1/purchase-orders/${id}/status`,
  {
    method: 'PUT',
    headers: {
      'X-API-Key': process.env.FORKCAST_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ status: 'delivered' }),
  }
);
PUT /purchase-orders/{id}

Requiere scope write — solo órdenes en estado pending

Reemplaza los items de una orden de compra existente. La orden debe estar en estado pending — si ya fue aprobada o entregada, retorna 409.

Body (JSON)

CampoTipoReq.
itemsItem[]

Misma estructura de items[] que en crear orden.

Respuestas

200Orden actualizada
400Items vacíos o inválidos
403Sin acceso a esta orden
404Orden no encontrada
409Orden no está en estado pending
curl https://app.forkcast.tech/api/v1/purchase-orders/42 \
  -X PUT \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "name": "Harina 000", "quantity": 15,
        "unit": "kg", "price": 850.00 }
    ]
  }'
await fetch(
  `https://app.forkcast.tech/api/v1/purchase-orders/${id}`,
  {
    method: 'PUT',
    headers: {
      'X-API-Key': process.env.FORKCAST_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ items }),
  }
);
POST /purchase-orders/bulk

Requiere scope write

Crea múltiples órdenes de compra en una sola transacción. Si alguna falla, todas se revierten. Ideal para importaciones desde ERP o sistemas de facturación.

Body (JSON)

CampoTipoReq.Descripción
ordersOrder[]Array de órdenes a crear

Cada elemento de orders[] tiene la misma estructura que el body de POST /purchase-orders.

Respuestas

201Todas las órdenes creadas
400Array vacío o items faltantes
403Scope write requerido
curl https://app.forkcast.tech/api/v1/purchase-orders/bulk \
  -X POST \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "orders": [
      {
        "supplierId": 5,
        "items": [
          { "name": "Harina 000", "quantity": 10,
            "unit": "kg", "price": 850 }
        ]
      },
      {
        "supplierId": 8,
        "items": [
          { "name": "Aceite", "quantity": 5,
            "unit": "l", "price": 1200 }
        ]
      }
    ]
  }'

Response 201

{
  "data": {
    "message": "2 órdenes de compra creadas exitosamente",
    "orders": [
      { "id": 43, "status": "pending", "isBillable": false, ... },
      { "id": 44, "status": "pending", "isBillable": false, ... }
    ]
  }
}

Response 422 · BULK_ORDER_OUT_OF_WINDOW

{
  "code": "BULK_ORDER_OUT_OF_WINDOW",
  "message": "2 proveedor(es) fuera de ventana de recepción.",
  "offenders": [
    { "supplierId": 5, "allowedDays": [1, 2], "nextAllowedDate": "2026-06-22" },
    { "supplierId": 8, "allowedDays": [3], "nextAllowedDate": "2026-06-24" }
  ]
}

El batch se rechaza completo (atomicidad) si al menos un proveedor cae fuera. offenders lista los proveedores ofensores con su próxima fecha disponible.

Perfil

GET /me

Retorna el perfil público del owner del API key. No requiere parámetros — la identidad se toma de la key.

Campos del response

CampoTipoDescripción
idintegerID del owner
namestringNombre comercial
usernamestringIdentificador único
addressstring | nullDirección
emailstring | nullEmail de contacto
phonestring | nullTeléfono
activebooleanEstado de la cuenta
deliveryZone (supplier)object | nullZona de entrega (solo proveedores)

Respuestas

200Perfil del owner
401API key inválida o ausente
curl "https://app.forkcast.tech/api/v1/me" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Response 200 — restaurante

{
  "data": {
    "id": 6,
    "name": "La Parrilla",
    "username": "laparrilla",
    "address": "Av. 9 de Julio 100",
    "email": "info@laparrilla.com",
    "phone": "+54911234567",
    "active": true
  }
}
GET /suppliers/:id

Retorna el perfil público de un proveedor. Solo disponible para restaurantes que hayan realizado al menos una orden de compra con ese proveedor.

Permiso requerido: debe existir al menos una orden de compra entre el restaurante caller y el proveedor solicitado.

Campos del response

CampoTipoDescripción
idintegerID del proveedor
namestringNombre comercial
usernamestringIdentificador único
addressstringDirección
phonestring | nullTeléfono
emailstring | nullEmail de contacto
deliveryZoneobject | nullZona de entrega
leadTimeDaysinteger | nullDías de entrega
minOrderAmountnumber | nullMonto mínimo de pedido
orderWindowDaysinteger[] | nullDías en los que el proveedor recibe pedidos (0=domingo, 6=sábado). null o array vacío = sin restricción. Ver Ventana de recepción.
activebooleanEstado de la cuenta

Respuestas

200Perfil del proveedor
403Sin acceso (caller es supplier, o nunca hubo órdenes con este proveedor)
404Proveedor no encontrado
curl "https://app.forkcast.tech/api/v1/suppliers/5" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Response 200

{
  "data": {
    "id": 5,
    "name": "Distribuidora Sur",
    "username": "distsur",
    "address": "Ruta 3 km 20, GBA Sur",
    "phone": "+54911000000",
    "email": "ventas@distsur.com",
    "deliveryZone": { "region": "GBA Sur" },
    "leadTimeDays": 2,
    "minOrderAmount": 5000,
    "orderWindowDays": [1, 2],
    "active": true
  }
}
GET /restaurants/:id

Retorna el perfil público de un restaurante. Solo disponible para proveedores que hayan recibido al menos una orden de compra de ese restaurante.

Permiso requerido: debe existir al menos una orden de compra del restaurante solicitado hacia el proveedor caller.

Campos del response

CampoTipoDescripción
idintegerID del restaurante
namestringNombre comercial
usernamestringIdentificador único
addressstring | nullDirección
emailstring | nullEmail de contacto
phonestring | nullTeléfono
activebooleanEstado de la cuenta

Respuestas

200Perfil del restaurante
403Sin acceso (caller es restaurante, o no hay órdenes de este restaurante)
404Restaurante no encontrado
curl "https://app.forkcast.tech/api/v1/restaurants/6" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Response 200

{
  "data": {
    "id": 6,
    "name": "La Parrilla",
    "username": "laparrilla",
    "address": "Av. 9 de Julio 100",
    "email": "info@laparrilla.com",
    "phone": "+54911234567",
    "active": true
  }
}
GET /franchises

Lista las franquicias activas de un restaurante master. Solo disponible para restaurantes con rol master.

Permiso requerido: el API key debe pertenecer a un restaurante configurado como master.

Respuestas

200Array de franquicias (puede ser vacío)
403Caller no es master (o es supplier)
curl "https://app.forkcast.tech/api/v1/franchises" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Response 200

{
  "data": [
    {
      "id": 10,
      "name": "Sucursal Norte",
      "username": "norte",
      "address": "Av. Norte 100",
      "email": "norte@parrilla.com",
      "phone": "+54911111111",
      "active": true
    }
  ]
}

Webhooks

GET /webhooks

Lista los webhooks configurados para el owner del API key.

Para más información sobre webhooks, payloads y verificación de firmas, ver la guía de webhooks.

curl https://app.forkcast.tech/api/v1/webhooks \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"
POST /webhooks

Requiere scope write

Registra un nuevo webhook. El secret retornado se usa para verificar la firma de cada evento. Guardalo de forma segura — no se puede recuperar después.

Body (JSON)

CampoTipoReq.
urlstring (URL)
eventsstring[]

Eventos disponibles: order.created, order.status_updated

curl https://app.forkcast.tech/api/v1/webhooks \
  -X POST \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-sistema.com/webhook",
    "events": ["order.created", "order.status_updated"]
  }'

Response 201

{
  "data": {
    "id": 1,
    "url": "https://tu-sistema.com/webhook",
    "events": ["order.created"],
    "secret": "a1b2c3d4e5f6...",
    "active": true
  }
}
POST /webhooks/test

Requiere scope write

Dispara un evento de prueba a una URL para validar la integración antes de registrar un webhook permanente. Útil para verificar la verificación HMAC, la conectividad y el parseo del payload del lado del receptor.

Body (JSON)

CampoTipoDescripción
urlstringURL destino del test
eventstringEvento simulado (ej: order.created)

Respuestas

200Test enviado. Incluye statusCode recibido del destino.
400URL inválida o evento desconocido
curl https://app.forkcast.tech/api/v1/webhooks/test \
  -X POST \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-sistema.com/webhook",
    "event": "order.created"
  }'

Response 200

{
  "data": {
    "delivered": true,
    "statusCode": 200,
    "event": "order.created"
  }
}
DELETE /webhooks/{id}

Requiere scope write

Elimina un webhook. No se recibirán más eventos en esa URL.

curl https://app.forkcast.tech/api/v1/webhooks/1 \
  -X DELETE \
  -H "X-API-Key: sk_live_xxxxxxxxxxxx"

Referencia

Ventana de recepción de pedidos

Los proveedores y las listas de precios pueden configurar días de recepción (orderWindowDays). Si se intenta crear un pedido fuera de esos días, la API responde con 422 ORDER_OUT_OF_WINDOW.

Formato de orderWindowDays

Array de enteros 0–6 donde 0 = domingo y 6 = sábado (criterio JavaScript Date.getDay()). null o array vacío significan sin restricción.

Precedencia

Cuando un pedido referencia una lista de precios, la ventana efectiva se calcula recorriendo la cadena de listas (de la hoja hacia las padres) buscando la primera orderWindowDays no nula. Si nadie en la cadena la define y la raíz es un proveedor, cae a la ventana del proveedor.

  1. Lista hoja (la del pedido) — si tiene orderWindowDays, gana.
  2. Cadena de listas padre — primera lista con orderWindowDays no nula.
  3. Proveedor raíz — fallback a supplier.orderWindowDays.
  4. Si nadie define ventana → sin restricción.

Zona horaria

El cálculo del "día actual" se hace en America/Argentina/Buenos_Aires.

Ejemplo · Proveedor con ventana

{
  "id": 5,
  "name": "Distribuidora Sur",
  "orderWindowDays": [1, 2]
}

Solo acepta pedidos lunes y martes.

Respuesta · pedido fuera de ventana

{
  "code": "ORDER_OUT_OF_WINDOW",
  "message": "Este proveedor solo recibe pedidos los días: 1, 2.",
  "supplierId": 5,
  "allowedDays": [1, 2],
  "nextAllowedDate": "2026-06-22"
}

Errores

Todos los errores siguen el mismo formato. El campo code identifica el tipo de error; message es legible.

HTTPSignificado
400Datos del request inválidos
401API key ausente o inválida
403Scope insuficiente o sin acceso al recurso
404Recurso no encontrado
422Regla de negocio rechaza la operación (ej: ventana de recepción del proveedor cerrada)
429Rate limit excedido
500Error interno

Códigos de error específicos (422)

codeSignificado
ORDER_OUT_OF_WINDOWEl proveedor o su lista de precios tiene orderWindowDays configurado y hoy no entra. Incluye supplierId, allowedDays y nextAllowedDate.
BULK_ORDER_OUT_OF_WINDOWEn un batch (/bulk), uno o más proveedores caen fuera de ventana. Incluye offenders con un objeto por proveedor ofensor. El batch se rechaza completo.

Formato de error

{
  "error": {
    "message": "API key inválida o revocada.",
    "code": "UNAUTHORIZED"
  },
  "meta": {
    "timestamp": "2026-05-20T12:00:00Z",
    "requestId": "uuid-..."
  }
}

Rate limits

Los límites se aplican por API key. Las respuestas incluyen headers con el estado actual del límite.

Lectura (GET) 100 req/min

Endpoints de consulta

Escritura (POST/PUT/DELETE) 30 req/min

Endpoints de modificación

Headers de rate limit

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1748347260

Al exceder el límite recibís un 429. Implementá backoff exponencial antes de reintentar.

Objetos

PurchaseOrder

CampoTipoDescripción
idintegerID único
restaurantIdintegerID del restaurante
supplierIdinteger | nullID del proveedor
statusstringpending | approved | rejected | delivered | cancelled
dateISO 8601Fecha del pedido
itemsItem[]Productos incluidos
isBillablebooleanSi la orden es facturable. Se siembra de la lista de precios al crear y solo el vendedor o el master pueden cambiarlo (vía endpoint interno) mientras la orden esté en pending.
createdAtISO 8601Timestamp de creación
updatedAtISO 8601Última actualización

Item

CampoTipoDescripción
namestringNombre del producto
quantitynumberCantidad pedida
unitstringkg, un, l, etc.
pricenumberPrecio unitario
packageSizenumberTamaño de cada unidad física (default 1)
unitsPerPackagenumberUnidades por SKU (default 1)
packagingNamestringNombre del packaging para display
normalizedNamestringNombre normalizado para búsqueda