Guía · 15 min

Conéctate a la Data API

La Data API sirve las business entities como endpoints REST versionados bajo /api/data/v{version}/{route}, por ejemplo /api/data/v1/customers. Un administrador decide qué entidades se exponen, qué campos expone cada versión y qué roles pueden listar, leer, crear, actualizar o eliminar. Un endpoint también puede llamar a un script module: recibe parámetros y responde con lo que devuelve el módulo. Esta guía es para quien escribe la integración: cómo conectarse, ya sea como servicio con una clave de API o como usuario que inicia sesión, y cómo llamar a los endpoints.

Antes de empezar

Pide cuatro cosas al administrador del espacio de trabajo:

  1. La dirección de la API. Es el mismo host con el que habla la propia aplicación. En la pantalla API Endpoints, el panel Try la muestra en su línea de curl.
  2. El endpoint y la versión, p. ej. customers versión 1. Solo responde una versión con estado Active o Deprecated; una Draft no.
  3. Un rol con permiso sobre ese endpoint para las operaciones que necesitas, configurado en la cuadrícula Who can call it del endpoint. Los roles admin y owner siempre pueden llamarlo todo.
  4. Una vía de acceso que tenga ese rol: una clave de API para un servicio, o una cuenta de usuario para una persona o un script rápido.

Para cualquier cosa que funcione de forma desatendida, como una sincronización nocturna, una tienda online o una herramienta de informes, usa una clave de API. No necesita contraseña, ni iniciar sesión, ni refrescar tokens, y da acceso a la Data API y a nada más.

Conecta un servicio con una clave de API

Un administrador crea un API client en la pantalla API Clients: un nombre, los roles que tiene y, opcionalmente, una fecha de caducidad. Los roles admin y owner no se pueden asignar a un API client, así que solo llega a lo que se ha concedido a sus roles en cada endpoint. La clave se muestra exactamente una vez, al crear el API client; solo se guarda una huella de ella, así que nadie puede consultarla más tarde, ni siquiera un administrador.

Envía la clave como token bearer en cada llamada:

API=https://api.example.com
KEY=ask_api_acme.q7Vt0r2Lk_dP9sXw3eNfH1mA6yZc4uBgJ8oTiR5vWnE

curl "$API/api/data/v1/customers" -H "Authorization: Bearer $KEY"
  • La clave solo funciona bajo /api/data. En cualquier otra parte del producto se rechaza, así que una clave filtrada no puede llegar a los demás datos de la aplicación ni a su configuración.
  • Sigue siendo válida hasta que caduca, se desactiva o se elimina el API client, o un administrador la rota para sustituirla por otra nueva. Cualquiera de estos cambios surte efecto en la siguiente llamada, que entonces responde 401.
  • Los cambios que hace el servicio llevan el nombre del API client como autor, y los business events lo ven como el usuario.
  • La clave lleva el nombre de su espacio de trabajo (ask_api_{workspace}.{secret}), así que no hace falta elegir ninguno. Guárdala en un almacén de secretos, nunca en el código.

Continúa con Llama a un endpoint.

Conéctate como usuario: inicia sesión

Usa una cuenta dedicada, no la de una persona. Un token de una sesión iniciada puede hacer todo lo que ese usuario puede hacer en la aplicación, no solo llamar a la Data API, así que da a la cuenta el rol concedido y nada más, y guarda su contraseña en un almacén de secretos. Para un servicio, una clave de API es la mejor opción.

Envía por POST el email y la contraseña de la cuenta a /api/v1/auth/login:

API=https://api.example.com

curl -X POST "$API/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email": "webshop-sync@example.com", "password": "..."}'

La respuesta contiene los tokens:

{
  "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "refreshToken": "q8Zr0dW3...",
  "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "expiresIn": 900,
  "redirectHost": null
}
  • token es el token de acceso. Envíalo en cada llamada. Caduca a los expiresIn segundos, 15 minutos por defecto.
  • refreshToken te da un par nuevo sin necesidad de la contraseña (consulta Mantén viva una sesión de usuario). Dura 30 días por defecto y solo funciona una vez.
  • idToken y redirectHost son para la aplicación del navegador; ignóralos.

Un inicio de sesión rechazado responde 401 con un code: invalid_credentials, disabled (la cuenta está desactivada) o no_tenant (la cuenta no pertenece a ningún espacio de trabajo activo). Demasiados intentos fallidos responden 429 con locked_out. Además, el inicio de sesión está limitado a 20 intentos por minuto por dirección IP, así que inicia sesión una vez y conserva la sesión, en lugar de iniciar sesión en cada llamada.

Llama a un endpoint

Pon la clave de API o el token de acceso en la cabecera Authorization; en los ejemplos siguientes, $TOKEN representa cualquiera de los dos. Una llamada de listado responde con una página de elementos y el total:

curl "$API/api/data/v1/customers?pageSize=2&sort=name" \
  -H "Authorization: Bearer $TOKEN"
{
  "items": [
    { "id": 1, "name": "Acme", "email": "info@acme.test", "creditLimit": 500 },
    { "id": 2, "name": "Globex", "email": "hello@globex.test", "creditLimit": 50 }
  ],
  "page": 1,
  "pageSize": 2,
  "totalCount": 3
}

Los nombres de los campos son los que el administrador ha dado a esta versión, y no tienen por qué coincidir con las columnas de la base de datos. Leer un elemento, crearlo, modificarlo y eliminarlo se hace así:

# One item
curl "$API/api/data/v1/customers/1" -H "Authorization: Bearer $TOKEN"

# Create: answers 201 with the stored item and a Location header
curl -X POST "$API/api/data/v1/customers" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Hooli", "email": "hi@hooli.test"}'

# Change some fields
curl -X PATCH "$API/api/data/v1/customers/4" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "sales@hooli.test"}'

# Delete: answers 204
curl -X DELETE "$API/api/data/v1/customers/4" -H "Authorization: Bearer $TOKEN"
  • PATCH cambia solo los campos que vienen en el cuerpo. PUT hace lo mismo, pero rechaza un cuerpo al que le falte un campo obligatorio.
  • El id siempre sale de la URL. Un id en el cuerpo de una creación se ignora, así que una creación nunca sobrescribe un elemento existente.
  • Los campos de solo lectura del cuerpo se ignoran, así que puedes reenviar un elemento exactamente tal como lo obtuviste. Un campo que la versión no conoce se rechaza con 400, lo que detecta las erratas.
  • Las escrituras se comportan exactamente igual que los guardados en la aplicación: se disparan los business events, se ejecuta la validación y las pantallas abiertas se refrescan.

Llama a un script module

Algunos endpoints ejecutan un script module en lugar de servir las filas de una entidad, para trabajo que va más allá de leer o escribir: calcular el precio de un pedido, reservar una franja horaria, generar un informe. Cada versión de un endpoint así responde a un solo método, y el documento OpenAPI dice cuál: POST recibe los parámetros como un objeto JSON en el cuerpo, GET los recibe en la cadena de consulta.

# A version called with POST: the parameters as a JSON object
curl -X POST "$API/api/data/v1/quote" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"quantity": 3, "express": true}'

# A version called with GET: the parameters in the query string
curl "$API/api/data/v1/planner-week?weekStart=2026-09-21" \
  -H "Authorization: Bearer $TOKEN"

La respuesta es lo que devuelve el módulo, en JSON:

{
  "quantity": 3,
  "total": 172.5,
  "deliveryDate": "2026-09-28"
}
  • Los nombres de los parámetros son los que el administrador dio a la versión. Cada valor se comprueba contra su tipo antes de que se ejecute el módulo: número entero, número, true o false, una fecha con o sin hora (2026-09-25, 2026-09-25T14:30:00Z), o texto. Un valor del tipo equivocado, un parámetro que la versión no conoce y un parámetro obligatorio que falta dan un 400; un null cuenta como omitido.
  • Un módulo que no devuelve nada responde 204 No Content. Uno que falla responde 500, con el propio mensaje del módulo en detail.
  • Llamar requiere un rol al que se le haya concedido Call en el endpoint. Un módulo que el administrador marcó como admin only solo responde a los roles admin y owner, así que una clave de API nunca lo alcanza.
  • El módulo se ejecuta de verdad, exactamente como cuando lo ejecuta la aplicación: lo que escribe dispara los business events, y el correo que envía sale de verdad.
  • Llamarlo con el otro método, o en una ruta de elemento como /quote/1, da un 405 cuya cabecera Allow indica el método que hay que usar.

Mantén viva una sesión de usuario

Esto solo se aplica a un usuario con sesión iniciada; una clave de API no necesita nada de ello. Antes de que caduque el token de acceso, o cuando una llamada responda 401, cambia el token de refresco por un par nuevo:

curl -X POST "$API/api/v1/auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "q8Zr0dW3..."}'

La respuesta tiene la misma forma que la del inicio de sesión, con un token de refresco nuevo. Guárdalo y descarta el anterior: un token de refresco solo funciona una vez. Volver a presentar uno ya usado pasado un minuto se trata como un robo y termina toda la sesión, tras lo cual tendrás que volver a iniciar sesión. Por eso, da a cada proceso o worker su propia sesión y nunca compartas un token de refresco entre ellos.

Un refresco responde 401 cuando la sesión ha terminado: ha caducado, la cuenta se ha desactivado o se ha quitado del espacio de trabajo, o se ha cerrado la sesión. Cuando pase eso, vuelve a iniciar sesión. Los cambios de rol llegan al token en el siguiente refresco, así que un rol revocado deja de funcionar en 15 minutos como mucho. Para cerrar tú mismo una sesión:

curl -X POST "$API/api/v1/auth/logout" \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "q8Zr0dW3..."}'

Una cuenta en más de un espacio de trabajo

El inicio de sesión elige un espacio de trabajo para la sesión: aquel al que pertenece la dirección a la que llamas, si la cuenta es miembro de él, y si no, el último que usó. Por eso una cuenta dedicada debería pertenecer a un solo espacio de trabajo. Si pertenece a varios, lístalos y cambia al que quieras con su tenantId:

# The workspaces this account belongs to
curl "$API/api/v1/auth/tenants" -H "Authorization: Bearer $TOKEN"

# A new token pair for one of them
curl -X POST "$API/api/v1/auth/switch-tenant" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tenantId": "3f1c2a9e-..."}'

El cambio responde con un par de tokens nuevo para ese espacio de trabajo; sigue con él igual que después de iniciar sesión.

Filtrado, ordenación y paginación

# Customers whose name contains "acme", highest credit limit first
curl -G "$API/api/data/v1/customers" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "filter[name][like]=acme" \
  --data-urlencode "filter[creditLimit][gte]=100" \
  --data-urlencode "sort=-creditLimit" \
  --data-urlencode "page=2" \
  --data-urlencode "pageSize=50"
ParámetroSignificado
pageNúmero de página, empezando por 1.
pageSizeElementos por página, 50 por defecto. Cada versión fija un máximo (100 salvo que el administrador lo haya cambiado); pedir más da un 400.
sortUn campo, con - delante para orden descendente. Sin él, primero los más recientes.
searchTexto que se busca en todos los campos que devuelve la versión.
filter[field]Igual al valor.
filter[field][op]Comparado con un operador, ver abajo. Si hay varios filtros, tienen que cumplirse todos.
OperadorCoincide con
eq, neIgual, distinto.
gt, gte, lt, lteMayor o menor que. Números, fechas y texto.
like, startswith, endswithTexto que contiene el valor, empieza por él o termina en él, sin distinguir mayúsculas de minúsculas. Solo campos de texto.
inCualquiera de una lista separada por comas: filter[id][in]=1,2,3.
nulltrue para valores vacíos, false para los que tienen valor.

Los valores se validan según el tipo del campo, así que filter[creditLimit]=lots da un 400 que indica el campo. Solo se puede filtrar, ordenar o buscar por los campos que devuelve la versión, y un parámetro desconocido se rechaza en lugar de ignorarse, así que una errata nunca te devuelve todo sin avisar. Los parámetros que empiezan por _ se ignoran, así que sirven para saltarse la caché.

Versiones

La versión forma parte de la ruta, y varias versiones del mismo endpoint pueden estar en servicio a la vez. Una versión publicada nunca cambia de forma que rompa tu integración: nunca se eliminan ni se renombran campos, y nada pasa a ser obligatorio. Del mismo modo, una versión que llama a un script module conserva su método y sus parámetros, y solo gana parámetros opcionales. Los cambios de ese tipo llegan como una versión nueva.

  • Las versiones Active y Deprecated responden.
  • Una versión que no existe, o que sigue en Draft, responde 404 y enumera las que sí existen.
  • Una versión Retired responde 410 Gone e indica a cuál pasarte.

Cada respuesta indica qué versiones existen. Una versión obsoleta también lo avisa, y dice cuándo desaparecerá:

HTTP/1.1 200 OK
api-supported-versions: 1, 2
api-deprecated-versions: 1
Deprecation: @1788220800
Sunset: Thu, 31 Dec 2026 00:00:00 GMT
Link: <https://api.example.com/api/data/v2/customers>; rel="successor-version"

Deprecation es el momento en que se marcó como obsoleta (una marca de tiempo Unix), Sunset la fecha en que deja de responder, si el administrador la ha fijado, y Link apunta a la versión siguiente. Registrar un aviso en el log cada vez que aparezca Deprecation es la forma más sencilla de enterarte a tiempo.

Errores

Un rechazo de la Data API es un problem document estándar (RFC 7807) cuyo detail explica qué estaba mal:

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

{
  "title": "Forbidden",
  "status": 403,
  "detail": "None of your roles may Delete on 'customers'."
}

Hay una excepción: cuando la validación de un business event rechaza una escritura, el cuerpo del 422 es { "message": "...", "errorColumns": [...] }, que indica los campos erróneos con los nombres de columna de la propia base de datos.

EstadoSignifica
400La petición está mal formada: un campo o parámetro desconocido, un valor de un tipo incorrecto, falta un campo obligatorio.
401No hay token ni clave, o lo enviado ya no es válido. Un usuario refresca el token y reintenta; una clave de API ha caducado o se ha rotado, o su API client se ha desactivado o eliminado.
403Ninguno de los roles de la cuenta puede hacer esto en este endpoint.
404No existe ese endpoint, esa versión o ese elemento.
405Esta versión no ofrece la operación, p. ej. eliminar, o se llamó a un endpoint de script module con el otro método.
409Los datos entran en conflicto: ya existe un valor que debe ser único, u otros registros siguen haciendo referencia a este.
410La versión se ha retirado.
422Una regla de negocio o una regla de la tabla ha rechazado los valores.
429Demasiadas peticiones. Espera un poco y reintenta.
500Falló el script module de un endpoint; detail contiene su mensaje.

Descripción OpenAPI

GET /api/data/openapi.json, llamado con tu token, responde con un documento OpenAPI 3 que contiene exactamente las versiones y operaciones que tu cuenta puede usar, con cada campo y su tipo. Impórtalo en Postman o Insomnia, o genera a partir de él un cliente tipado. Vuelve a descargarlo cuando el administrador publique una versión nueva.

Ejemplos en Python

Con una clave de API, basta con la cabecera:

import os
import requests

API = os.environ["ARCHESTACK_API"]
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['ARCHESTACK_API_KEY']}"

r = session.get(f"{API}/api/data/v1/customers", params={"pageSize": 100}, timeout=30)
r.raise_for_status()
for customer in r.json()["items"]:
    print(customer["id"], customer["name"])

Si te conectas como usuario, hay que ocuparse de la sesión. Este ejemplo inicia sesión, refresca el token un minuto antes de que caduque, se recupera de un 401, lee todas las páginas y crea un elemento. Pon la dirección y las credenciales en variables de entorno.

import os
import time
import requests

API = os.environ["ARCHESTACK_API"]            # e.g. https://api.example.com
EMAIL = os.environ["ARCHESTACK_EMAIL"]
PASSWORD = os.environ["ARCHESTACK_PASSWORD"]


class DataApi:
    """One signed-in session. Give every process or worker its own."""

    def __init__(self):
        self.token = None
        self.refresh_token = None
        self.expires_at = 0.0

    def _keep(self, body):
        self.token = body["token"]
        self.refresh_token = body["refreshToken"]  # always replace it: each one works once
        self.expires_at = time.time() + body["expiresIn"] - 60

    def sign_in(self):
        r = requests.post(f"{API}/api/v1/auth/login",
                          json={"email": EMAIL, "password": PASSWORD}, timeout=30)
        r.raise_for_status()
        self._keep(r.json())

    def refresh(self):
        r = requests.post(f"{API}/api/v1/auth/refresh",
                          json={"refreshToken": self.refresh_token}, timeout=30)
        if r.status_code == 401:  # the session ended: start a new one
            return self.sign_in()
        r.raise_for_status()
        self._keep(r.json())

    def call(self, method, path, **kwargs):
        if self.token is None:
            self.sign_in()
        elif time.time() >= self.expires_at:
            self.refresh()
        r = self._send(method, path, **kwargs)
        if r.status_code == 401:  # expired early or revoked: refresh once and retry
            self.refresh()
            r = self._send(method, path, **kwargs)
        return r

    def _send(self, method, path, **kwargs):
        headers = {"Authorization": f"Bearer {self.token}"}
        return requests.request(method, f"{API}/api/data{path}",
                                headers=headers, timeout=30, **kwargs)


api = DataApi()

# Every page of customers
page = 1
while True:
    r = api.call("GET", "/v1/customers", params={"page": page, "pageSize": 100})
    r.raise_for_status()
    body = r.json()
    for customer in body["items"]:
        print(customer["id"], customer["name"])
    if page * body["pageSize"] >= body["totalCount"]:
        break
    page += 1

# Create one
r = api.call("POST", "/v1/customers", json={"name": "Hooli", "email": "hi@hooli.test"})
if r.status_code == 201:
    print("created", r.json()["id"])
else:
    print("refused:", r.json().get("detail"))
Entornos de prueba: ahí solo pueden llamar a la API la cuenta propietaria de la prueba y los API clients que cree esa cuenta.

Hacia dónde ir después

  • Business Entities - de dónde lee un endpoint, y por qué una entidad que hace join con una tabla uno a muchos devuelve un elemento por cada fila unida.
  • User Management - crea la cuenta de integración y su rol.