Saltar al contenido
Entrar Pedir una demo

Desarrolladores

Tu almacén, en tu ERP.

La API v1 lee el catálogo, el stock, los movimientos, los avisos y los costes de tu organización, y registra retiradas y reposiciones. JSON por HTTPS, con una clave que creas tú.

Empezar

Empieza en tres pasos.

  1. Crea una clave.

    En FASTIK, Perfil › Integraciones › Claves de API. Solo las responsables de tu organización pueden crearla, con su cuenta personal, y la verás una sola vez: cópiala entonces.

  2. Prueba la conexión.

    GET /v1/me devuelve la clave y tu organización. Si da 200, ya está.

  3. Pide lo que necesites.

    Todas las rutas cuelgan de https://api.fastik.es/v1/. Las respuestas son JSON y las fechas, ISO 8601 en UTC.

¿Integraciones dice «No contratado»? La integración es un extra: pídela a FASTIK.

Petición
curl https://api.fastik.es/v1/me \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "key": {
    "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
    "name": "ERP",
    "prefix": "fk_live_3f9a1c07",
    "scopes": ["read"],
    "expires_at": "2027-10-28T09:12:00.000Z"
  },
  "organization": { "name": "Tu organización" }
}

La clave

Una clave, siempre en la cabecera.

  • fk_live_ y 64 caracteres hexadecimales. Las de prueba empiezan por fk_test_.
  • Va en Authorization: Bearer. En la dirección no vale: la petición da 400 invalid_parameter.
  • Caduca a los 90 días, al año o nunca: lo eliges al crearla. Puedes revocarla cuando quieras.
  • FASTIK no puede volver a enseñártela. Si la pierdes, revócala y crea otra.
Cabecera
Authorization: Bearer fk_live_3f9a1c07••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Respuesta · 401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/problem+json

{
  "type": "https://fastik.es/desarrolladores/errores#unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "code": "unauthorized",
  "detail": "Falta la clave de API, no es válida o ya no está activa."
}

Permisos

Cada clave, con lo justo.

Al crear la clave eliges qué puede hacer. Si pide una ruta sin su permiso, la respuesta es 403 insufficient_scope.

Quién hizo cada movimiento solo viaja si una responsable enciende «Datos de personas» en Integraciones.

read
El catálogo, las familias, el stock, los movimientos y los avisos. Toda clave lo lleva.
costs:read
Los costes unitarios y las compras.
movements:write
Registrar retiradas y reposiciones con POST /v1/movements.
catalog:write
Dar de alta y actualizar productos con POST /v1/products.

Cupos

Cupos por minuto.

120
lecturas por clave
30
escrituras por clave
300
peticiones por organización

Las respuestas que pasan por el cupo dicen, en RateLimit-Remaining, cuánto te queda en el minuto. Si te pasas, la respuesta es 429 rate_limited con Retry-After: espera esos segundos y repite.

Respuesta · 200
HTTP/1.1 200 OK
RateLimit-Limit: 120
RateLimit-Remaining: 117
RateLimit-Reset: 42
Respuesta · 429
HTTP/1.1 429 Too Many Requests
Retry-After: 18
Content-Type: application/problem+json

Paginación

De 50 en 50, hasta 200.

  • Las listas devuelven data y next_cursor. Pide la página siguiente con cursor; con next_cursor: null, has terminado.
  • limit va de 1 a 200; sin él, 50. /v1/families llega en una sola página.
  • El cursor vale para la misma clave, la misma ruta y los mismos filtros. Si no, 400 invalid_cursor.
  • Ignora los campos que no conozcas: una versión menor puede añadir alguno.
Petición
curl -G https://api.fastik.es/v1/products \
  -d limit=200 \
  -d cursor=<next_cursor> \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "sku": "FIL-0042",
      "name": "Filtro de agua",
      "family": "fontaneria",
      "unit": "uds",
      "current_stock": 12,
      "min_stock": 4,
      "reorder_qty": 10,
      "archived_at": null,
      "created_at": "2026-10-28T09:12:00.000Z",
      "updated_at": "2026-10-28T09:12:00.000Z"
    }
  ],
  "next_cursor": null
}

Errores

Cada error, con su código.

Los errores llegan en application/problem+json (RFC 9457). Decide por code, que no cambia; detail es para personas.

Cada type enlaza a su fila de esta tabla: https://fastik.es/desarrolladores/errores#<code>.

En un lote no válido, errors dice el elemento (index), el campo (field) y el motivo: required, invalid_type, invalid_value, out_of_range, not_allowed, invalid_characters (un espacio que no es el normal, un carácter invisible o de control) o not_normalized (un texto fuera de NFC).

Respuesta · 403
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://fastik.es/desarrolladores/errores#insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "Esta clave no tiene el permiso que pide la ruta."
}
Los códigos de error
Código HTTP Qué pasa Qué hacer
#unauthorized 401 Falta la clave de API, no es válida o ya no está activa. Revisa la cabecera Authorization. Si la clave caducó o se revocó, crea otra.
#insufficient_scope 403 Esta clave no tiene el permiso que pide la ruta. Crea una clave con ese permiso.
#organization_suspended 403 La organización está suspendida. Habla con la responsable de tu organización.
#integrations_not_enabled 403 La integración no está habilitada para esta organización. Pide la integración a FASTIK.
#not_found 404 Esa ruta no existe, o el producto no es de tu organización. Revisa la ruta y el id.
#method_not_allowed 405 Esa ruta no admite ese método. Usa uno de los de la cabecera Allow.
#invalid_parameter 400 Un parámetro no es válido, o la clave viaja en la dirección. Corrige lo que dice detail. La clave, solo en Authorization.
#invalid_body 400 El cuerpo no es un lote válido. Corrige los elementos de errors y reenvía el lote.
#payload_too_large 413 El cuerpo pasa de 256 KB. Parte el lote: hasta 100 elementos.
#unsupported_media_type 415 El cuerpo no va en application/json. Envía Content-Type: application/json.
#invalid_cursor 400 El cursor no es de esta clave, de esta ruta o de estos filtros. Vuelve a pedir la primera página, sin cursor.
#rate_limited 429 Demasiadas peticiones en el minuto. Espera los segundos de Retry-After y repite.
#internal_error 500 Algo ha fallado. Repite en un momento. Con el mismo id, un movimiento no se aplica dos veces.
#temporarily_unavailable 503 Esta parte se está actualizando. Repite pasados los segundos de Retry-After.

Rutas

Las rutas de la v1.

Base: https://api.fastik.es. Todas piden la clave salvo /v1/openapi.json, que es público. Las listas se paginan con limit y cursor.

Las rutas de la API v1
Método Ruta Qué devuelve Permiso
GET /v1/me La clave y la organización: para «Probar la conexión». read
GET /v1/families Las familias del catálogo, en una sola página. read
GET /v1/products Los productos, por updated_at. Sin updated_since, sin los archivados.updated_since · sku · family · include_archived read
POST /v1/products Un lote de 1 a 100 altas o actualizaciones por sku. Nunca cambia el stock. catalog:write
GET /v1/products/{id} Un producto; 404 si no es de tu organización. read
GET /v1/movements Los movimientos, por recorded_at, sin los de los 2 últimos segundos.since · until · product_id · type read
POST /v1/movements Un lote de 1 a 100 retiradas o reposiciones. Repetir un id no aplica nada dos veces. movements:write
GET /v1/alerts Los avisos de stock abiertos; con include_resolved_since, también los cerrados.include_resolved_since read
GET /v1/costs El coste unitario actual de cada producto, sin impuestos, por updated_at.updated_since costs:read
GET /v1/purchases Las compras, por registered_at; las anuladas, con voided_at.since costs:read
GET /v1/openapi.json Este documento, en OpenAPI 3.1. Público, sin clave. –

Así es la ficha de cada ruta:

GET /v1/me

La clave con la que llamas y tu organización. Permiso: read.

  • Si da 200, la clave vale y la integración está habilitada.
Petición
curl https://api.fastik.es/v1/me \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "key": {
    "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
    "name": "ERP",
    "prefix": "fk_live_3f9a1c07",
    "scopes": ["read"],
    "expires_at": "2027-10-28T09:12:00.000Z"
  },
  "organization": { "name": "Tu organización" }
}

GET /v1/families

Las familias del catálogo, todas en una página. Permiso: read.

  • Sin limit ni cursor: next_cursor siempre es null.
Petición
curl https://api.fastik.es/v1/families \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "key": "fontaneria",
      "name": "Fontanería"
    }
  ],
  "next_cursor": null
}

GET /v1/products

Los productos de tu organización, por updated_at. Permiso: read.

Parámetros

updated_since
Fecha ISO 8601: los cambiados desde entonces.
sku
Un SKU exacto.
family
La key de una familia de /v1/families.
include_archived
true o false. Sin él, los archivados solo salen con updated_since.
limit
De 1 a 200; sin él, 50.
cursor
El next_cursor de la página anterior.
Petición
curl https://api.fastik.es/v1/products \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "sku": "FIL-0042",
      "name": "Filtro de agua",
      "family": "fontaneria",
      "unit": "uds",
      "current_stock": 12,
      "min_stock": 4,
      "reorder_qty": 10,
      "archived_at": null,
      "created_at": "2026-10-28T09:12:00.000Z",
      "updated_at": "2026-10-28T09:12:00.000Z"
    }
  ],
  "next_cursor": null
}

POST /v1/products

Da de alta o actualiza de 1 a 100 productos, por la referencia de tu ERP. Permiso: catalog:write.

  • El sku es la llave, exacta: si existe en tu organización se actualiza; si no, se da de alta. Cada sku, una vez por lote.
  • En el alta, name y unit_cost (sin impuestos) son obligatorios; sin ellos, rejected con name_required o unit_cost_required.
  • Lo que no envías se queda como está, también el nombre. family, unit, min_stock y reorder_qty son opcionales.
  • sku y name no se arreglan: un espacio que no sea el normal, un carácter invisible o de control da invalid_characters, y un texto fuera de NFC, not_normalized.
  • unit_cost lleva como mucho 4 decimales; el ruido de la coma flotante (0.30000000000000004) se redondea.
  • Gana el último que escribe: si alguien cambió el producto en la app, tu envío lo sustituye. Manda solo lo que cambió en tu ERP.
  • El stock no se toca: un alta nace a 0 y el stock inicial va con un restock en POST /v1/movements.
  • Reenviar el mismo lote no duplica nada: vuelve unchanged. Un producto archivado vuelve rejected con product_archived; también uno de una importación de Excel que se deshizo, hasta que alguien lo recupere en la app.
  • Tu organización puede deshacer uno de tus lotes: las altas que nadie ha movido se archivan (tus envíos siguientes de ese sku vuelven rejected con product_archived) y lo que cambió vuelve al valor de antes. Si tu ERP solo manda lo que cambia, no volverá a enviar esos valores: reenvía el producto entero para recuperarlos.
Petición
curl -X POST https://api.fastik.es/v1/products \
  -H "Authorization: Bearer $FASTIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d @lote.json
Cuerpo · lote.json
{
  "products": [
    {
      "sku": "FIL-0042",
      "name": "Filtro de agua",
      "family": "fontaneria",
      "unit": "uds",
      "min_stock": 4,
      "reorder_qty": 10,
      "unit_cost": 3.5
    },
    {
      "sku": "LED-E27",
      "name": "Bombilla LED E27",
      "unit_cost": 2.1
    }
  ]
}
Respuesta · 200
{
  "results": [
    {
      "sku": "FIL-0042",
      "status": "updated",
      "product_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6"
    },
    {
      "sku": "LED-E27",
      "status": "created",
      "product_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f"
    }
  ]
}

GET /v1/products/{id}

Un producto de tu organización. Permiso: read.

Parámetros

id
En la ruta: el UUID del producto.
  • Si el id no es de tu organización, 404 not_found, como si no existiera.
Petición
curl https://api.fastik.es/v1/products/0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6 \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
  "sku": null,
  "name": "Bombilla LED E27",
  "family": null,
  "unit": "uds",
  "current_stock": 0,
  "min_stock": 0,
  "reorder_qty": 0,
  "archived_at": null,
  "created_at": "2026-10-28T09:12:00.000Z",
  "updated_at": "2026-10-28T09:12:00.000Z"
}

GET /v1/movements

Los movimientos de tu organización, por recorded_at. Permiso: read.

Parámetros

since
Obligatorio. Fecha ISO 8601: desde cuándo.
until
Fecha ISO 8601: hasta cuándo.
product_id
UUID de un producto.
type
withdraw, restock o adjust.
limit
De 1 a 200; sin él, 50.
cursor
El next_cursor de la página anterior.
  • No devuelve los de los 2 últimos segundos: llegan en la lectura siguiente.
  • person solo viaja con «Datos de personas» encendido.
Petición
curl "https://api.fastik.es/v1/movements?since=2026-10-27T23:55:00Z" \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "product_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "type": "withdraw",
      "quantity": 2,
      "delta": -2,
      "stock_after": 10,
      "reason": null,
      "occurred_at": "2026-10-28T09:12:00.000Z",
      "recorded_at": "2026-10-28T09:12:00.000Z",
      "source": "api",
      "api_key_id": "1b1b1b1b-1b1b-4b1b-8b1b-1b1b1b1b1b1b"
    }
  ],
  "next_cursor": null
}

POST /v1/movements

Un lote de 1 a 100 retiradas o reposiciones. Permiso: movements:write.

  • Cada movimiento lleva su id, un UUID que generas tú, y el producto por product_id o por sku.
  • type: withdraw o restock. quantity: un entero positivo. occurred_at es opcional.
  • adjust solo sale al leer: al escribir, el lote da invalid_body con not_allowed.
  • Repetir un id no aplica nada dos veces: si no sabes si llegó, reenvía el mismo lote.
  • Cada elemento vuelve applied o rejected, con su reason. El cuerpo, en application/json y hasta 256 KB.
Petición
curl -X POST https://api.fastik.es/v1/movements \
  -H "Authorization: Bearer $FASTIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d @lote.json
Cuerpo · lote.json
{
  "movements": [
    {
      "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "product_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "type": "withdraw",
      "quantity": 2,
      "occurred_at": "2026-10-28T09:12:00.000Z"
    },
    {
      "id": "7b1a4c8f-2d3e-4f50-9a6b-1c2d3e4f5a6b",
      "sku": "FIL-0042",
      "type": "restock",
      "quantity": 10
    }
  ]
}
Respuesta · 200
{
  "results": [
    {
      "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "status": "applied",
      "stock": 10
    },
    {
      "id": "7b1a4c8f-2d3e-4f50-9a6b-1c2d3e4f5a6b",
      "status": "rejected",
      "stock": null,
      "reason": "product_not_found"
    }
  ]
}

GET /v1/alerts

Los avisos de stock de tu organización. Permiso: read.

Parámetros

include_resolved_since
Fecha ISO 8601: suma los cerrados desde entonces.
limit
De 1 a 200; sin él, 50.
cursor
El next_cursor de la página anterior.
Petición
curl https://api.fastik.es/v1/alerts \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "kind": "below_minimum",
      "product": {
        "id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
        "sku": "FIL-0042",
        "name": "Filtro de agua",
        "unit": "uds",
        "current_stock": 2,
        "min_stock": 4
      },
      "created_at": "2026-10-28T09:12:00.000Z",
      "resolved_at": null
    }
  ],
  "next_cursor": null
}

GET /v1/costs

El coste unitario actual de cada producto, sin impuestos, por updated_at. Permiso: costs:read.

Parámetros

updated_since
Fecha ISO 8601: los cambiados desde entonces.
limit
De 1 a 200; sin él, 50.
cursor
El next_cursor de la página anterior.
Petición
curl https://api.fastik.es/v1/costs \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "product_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "unit_cost": 3.5,
      "tax_rate": null,
      "updated_at": "2026-10-28T09:12:00.000Z"
    }
  ],
  "currency": "EUR",
  "tax_label": "IGIC",
  "default_tax_rate": 0.07,
  "prices_include_tax": false,
  "next_cursor": null
}

GET /v1/purchases

Las compras de tu organización, por registered_at. Permiso: costs:read.

Parámetros

since
Obligatorio. Fecha ISO 8601: desde cuándo.
limit
De 1 a 200; sin él, 50.
cursor
El next_cursor de la página anterior.
  • Una compra anulada sigue saliendo, con su voided_at.
Petición
curl "https://api.fastik.es/v1/purchases?since=2026-10-27T23:55:00Z" \
  -H "Authorization: Bearer $FASTIK_API_KEY"
Respuesta · 200
{
  "data": [
    {
      "id": "6a0f3b7e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "movement_id": "7b1a4c8f-2d3e-4f50-9a6b-1c2d3e4f5a6b",
      "product_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
      "quantity": 10,
      "unit_cost": 3.5,
      "urgent": false,
      "registered_at": "2026-10-28T09:12:00.000Z",
      "voided_at": null
    }
  ],
  "next_cursor": null
}

GET /v1/openapi.json

Este documento, en OpenAPI 3.1. Público, sin clave.

Petición
curl https://api.fastik.es/v1/openapi.json
Respuesta · 200
{ "openapi": "3.1.0" }

Guías

Dos guías cortas.

Power Automate, Make o Zapier

  1. Usa el paso HTTP genérico de tu herramienta. No hace falta un conector de FASTIK.

  2. Guarda la clave en una conexión o un secreto, nunca en un paso visible: quien abra el flujo la vería.

  3. Prueba con GET /v1/me. Luego programa la ruta que necesites; por ejemplo, GET /v1/alerts cada hora.

  4. Si llega un 429, deja que el flujo reintente pasados los segundos de Retry-After.

Llevar el consumo a tu ERP

  1. Una vez al día, pide GET /v1/movements con since = la hora de la lectura anterior menos 5 minutos.

  2. Sigue next_cursor hasta que sea null.

  3. Descarta los id que ya tengas: el solape repite algunos a propósito.

  4. Guarda la hora de esta lectura para la de mañana.

Petición diaria
curl -G https://api.fastik.es/v1/movements \
  -d since=2026-10-27T23:55:00Z \
  -d type=withdraw \
  -H "Authorization: Bearer $FASTIK_API_KEY"

Versión 1.0.0. Un cambio compatible —un campo o una ruta nuevos— sube el número menor y se anota aquí. Uno incompatible sería una /v2, con 6 meses de convivencia.

Descargar openapi-v1.json →