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:
- 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.
- El endpoint y la versión, p. ej.
customersversión 1. Solo responde una versión con estado Active o Deprecated; una Draft no. - 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.
- 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
} -
tokenes el token de acceso. Envíalo en cada llamada. Caduca a losexpiresInsegundos, 15 minutos por defecto. -
refreshTokente 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. idTokenyredirectHostson 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
iden 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 un400; unnullcuenta como omitido. -
Un módulo que no devuelve nada responde
204 No Content. Uno que falla responde500, con el propio mensaje del módulo endetail. - 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 un405cuya cabeceraAllowindica 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ámetro | Significado |
|---|---|
page | Número de página, empezando por 1. |
pageSize | Elementos 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. |
sort | Un campo, con - delante para orden descendente. Sin él, primero los más recientes. |
search | Texto 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. |
| Operador | Coincide con |
|---|---|
eq, ne | Igual, distinto. |
gt, gte, lt, lte | Mayor o menor que. Números, fechas y texto. |
like, startswith, endswith | Texto que contiene el valor, empieza por él o termina en él, sin distinguir mayúsculas de minúsculas. Solo campos de texto. |
in | Cualquiera de una lista separada por comas: filter[id][in]=1,2,3. |
null | true 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
404y enumera las que sí existen. - Una versión Retired responde
410 Gonee 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.
| Estado | Significa |
|---|---|
400 | La petición está mal formada: un campo o parámetro desconocido, un valor de un tipo incorrecto, falta un campo obligatorio. |
401 | No 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. |
403 | Ninguno de los roles de la cuenta puede hacer esto en este endpoint. |
404 | No existe ese endpoint, esa versión o ese elemento. |
405 | Esta versión no ofrece la operación, p. ej. eliminar, o se llamó a un endpoint de script module con el otro método. |
409 | Los datos entran en conflicto: ya existe un valor que debe ser único, u otros registros siguen haciendo referencia a este. |
410 | La versión se ha retirado. |
422 | Una regla de negocio o una regla de la tabla ha rechazado los valores. |
429 | Demasiadas peticiones. Espera un poco y reintenta. |
500 | Falló 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.