Ligar-se à Data API
A Data API disponibiliza Business Entities como endpoints REST versionados sob
/api/data/v{version}/{route}, por exemplo
/api/data/v1/customers. Um admin decide que entidades ficam expostas, que campos
cada versão disponibiliza e que roles podem listar, ler, criar, atualizar ou eliminar. Um endpoint também pode chamar um Script Module: recebe parâmetros e
responde com o que o módulo devolve. Este guia
é para quem escreve a integração: como fazer a ligação, enquanto serviço com uma chave de API ou
enquanto utilizador que inicia sessão, e como chamar os endpoints.
Antes de começar
Peça quatro coisas ao admin do espaço de trabalho:
- O endereço da API. O mesmo host com que a própria aplicação comunica. No ecrã API Endpoints, o painel Try mostra-o na sua linha curl.
- O endpoint e a versão, por exemplo
customersversão 1. Só uma versão com o estado Active ou Deprecated responde; uma versão Draft não. - Uma role concedida nesse endpoint para as operações de que precisa, definida na grelha Who can call it do endpoint. As roles admin e owner podem sempre chamar tudo.
- Um meio de acesso com essa role: uma chave de API para um serviço, ou uma conta de utilizador para uma pessoa ou um script rápido.
Para tudo o que corre sem supervisão, como uma sincronização noturna, uma loja online ou uma ferramenta de relatórios, use uma chave de API. Não precisa de palavra-passe, de início de sessão nem de refresh, e abre a Data API e mais nada.
Ligar um serviço com uma chave de API
Um admin cria um API client no ecrã API Clients: um nome, as roles que detém e, opcionalmente, uma data de expiração. As roles admin e owner não podem ser atribuídas a um API client, por isso este só chega ao que foi concedido às suas roles em cada endpoint. A chave é mostrada exatamente uma vez, quando o API client é criado; só fica guardada uma impressão digital dela, por isso ninguém a pode consultar mais tarde, nem mesmo um admin.
Envie a chave como bearer token em todas as chamadas:
API=https://api.example.com
KEY=ask_api_acme.q7Vt0r2Lk_dP9sXw3eNfH1mA6yZc4uBgJ8oTiR5vWnE
curl "$API/api/data/v1/customers" -H "Authorization: Bearer $KEY" -
A chave só funciona sob
/api/data. Em qualquer outro ponto do produto é recusada, por isso uma chave exposta não consegue chegar aos outros dados da aplicação nem às suas definições. -
Mantém-se válida até expirar, até o API client ser desativado ou eliminado, ou até um admin a
rodar, trocando-a por uma nova. Qualquer uma destas situações tem efeito na chamada
seguinte, que passa a devolver
401. - As alterações feitas pelo serviço ficam com o nome do API client como autor, e os Business Events veem-no como o utilizador.
-
A chave indica o seu espaço de trabalho (
ask_api_{workspace}.{secret}), por isso não é preciso escolher um espaço de trabalho. Guarde-a num cofre de segredos, nunca no código.
Continue em Chamar um endpoint.
Ligar-se como utilizador: iniciar sessão
Use uma conta dedicada, não a de uma pessoa. O token de uma sessão iniciada pode fazer tudo o que esse utilizador pode fazer na aplicação, não apenas chamar a Data API, por isso dê à conta a role concedida e mais nada, e guarde a respetiva palavra-passe num cofre de segredos. Para um serviço, uma chave de API é a melhor opção.
Envie o email e a palavra-passe da conta num POST para /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": "..."}' A resposta contém os tokens:
{
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"refreshToken": "q8Zr0dW3...",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"expiresIn": 900,
"redirectHost": null
} -
tokené o token de acesso. Envie-o em todas as chamadas. Expira ao fim deexpiresInsegundos, 15 minutos por omissão. -
refreshTokendá-lhe um novo par sem a palavra-passe (consulte Manter ativa uma sessão de utilizador). Dura 30 dias por omissão e funciona exatamente uma vez. idTokeneredirectHostdestinam-se à aplicação no browser; ignore-os.
Um início de sessão recusado devolve 401 com um code:
invalid_credentials, disabled (a conta está desativada) ou
no_tenant (a conta não pertence a nenhum espaço de trabalho ativo). Demasiadas
tentativas falhadas devolvem 429 com locked_out. O início de sessão
está também limitado a 20 tentativas por minuto por endereço IP, por isso inicie sessão uma vez e
mantenha a sessão, em vez de iniciar sessão a cada chamada.
Chamar um endpoint
Coloque a chave de API ou o token de acesso no cabeçalho Authorization; nos
exemplos abaixo, $TOKEN representa qualquer um dos dois. Uma chamada de listagem
devolve uma página de itens e o 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
} Os nomes dos campos são os que o admin deu a esta versão, e não têm de corresponder às colunas da base de dados. Ler um item, criar, alterar e eliminar fica assim:
# 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 altera apenas os campos presentes no body. PUT faz o mesmo, mas recusa um body que omita um campo obrigatório.
-
O id vem sempre do URL. Um
idno body de uma criação é ignorado, por isso uma criação nunca sobrescreve um item existente. -
Os campos só de leitura num body são ignorados, por isso pode reenviar um item exatamente como
o obteve. Um campo que a versão não conhece é recusado com
400, o que apanha erros de digitação. - As escritas comportam-se exatamente como as gravações na aplicação: os Business Events disparam, a validação corre e os ecrãs abertos são atualizados.
Chamar um Script Module
Alguns endpoints executam um Script Module em vez de servir as linhas de uma entidade, para trabalho que vai além de uma leitura ou de uma escrita: calcular o preço de uma encomenda, reservar um horário, produzir um relatório. Cada versão de um endpoint destes responde a um único método, e o documento OpenAPI diz qual: POST recebe os parâmetros como um objeto JSON no body, GET recebe-os na query string.
# 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" A resposta é o que o módulo devolve, em JSON:
{
"quantity": 3,
"total": 172.5,
"deliveryDate": "2026-09-28"
} -
Os nomes dos parâmetros são os que o admin deu à versão. Cada valor é verificado face ao seu
tipo antes de o módulo correr: número inteiro, número, true ou false, uma data com ou sem hora
(
2026-09-25,2026-09-25T14:30:00Z), ou texto. Um valor do tipo errado, um parâmetro que a versão não conhece e um parâmetro obrigatório em falta dão um400; umnullconta como omitido. -
Um módulo que não devolve nada responde
204 No Content. Um que falha responde500, com a mensagem do próprio módulo emdetail. - A chamada exige uma role à qual foi concedida a operação Call no endpoint. Um módulo que o admin marcou como admin only só responde às roles admin e owner, por isso uma chave de API nunca o alcança.
- O módulo corre a sério, exatamente como quando a aplicação o corre: o que escreve dispara os Business Events, e o e-mail que envia sai mesmo.
-
Uma chamada com o outro método, ou num caminho de item como
/quote/1, dá um405cujo cabeçalhoAllowindica o método a usar.
Manter ativa uma sessão de utilizador
Isto aplica-se apenas a um utilizador com sessão iniciada; uma chave de API não precisa de nada
disto. Antes de o token de acesso expirar, ou quando uma chamada devolver 401,
troque o refresh token por um novo par:
curl -X POST "$API/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' A resposta tem a mesma forma que a do início de sessão, com um novo refresh token. Guarde-o e descarte o antigo: um refresh token só funciona uma vez. Voltar a apresentar um já usado depois de passado um minuto é tratado como roubo e termina a sessão inteira, após o que terá de iniciar sessão de novo. Por isso, dê a cada processo ou worker a sua própria sessão, e nunca partilhe um refresh token entre eles.
Um refresh devolve 401 quando a sessão terminou: expirou, a conta foi desativada ou
removida do espaço de trabalho, ou alguém terminou a sessão. Nesse caso, inicie sessão de novo.
As alterações de roles chegam ao token no refresh seguinte, por isso uma role revogada deixa de
funcionar num prazo de 15 minutos. Para terminar uma sessão por iniciativa própria:
curl -X POST "$API/api/v1/auth/logout" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' Uma conta em mais do que um espaço de trabalho
O início de sessão escolhe um espaço de trabalho para a sessão: aquele a que pertence o endereço
que chama, se a conta for membro dele, caso contrário o último que a conta usou. É por isso que
uma conta dedicada deve pertencer a um único espaço de trabalho. Se pertencer a vários, liste-os
e mude com o tenantId daquele que pretende:
# 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-..."}' A mudança devolve um novo par de tokens para esse espaço de trabalho; continue com ele como depois de um início de sessão.
Filtrar, ordenar e paginar
# 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 da página, a começar em 1. |
pageSize | Itens por página, 50 por omissão. Cada versão define um máximo (100, salvo se o admin o tiver alterado); pedir mais devolve 400. |
sort | Um campo, com - à frente para ordem descendente. Sem ele, os mais recentes primeiro. |
search | Texto procurado em todos os campos que a versão devolve. |
filter[field] | Igual ao valor. |
filter[field][op] | Comparado com um operador, ver abaixo. Com vários filtros, todos têm de corresponder. |
| Operador | Corresponde a |
|---|---|
eq, ne | Igual, diferente. |
gt, gte, lt, lte | Maior ou menor que. Números, datas e texto. |
like, startswith, endswith | Texto que contém o valor, começa ou termina com ele, sem distinguir maiúsculas de minúsculas. Só campos de texto. |
in | Qualquer valor de uma lista separada por vírgulas: filter[id][in]=1,2,3. |
null | true para valores vazios, false para valores preenchidos. |
Os valores são validados contra o tipo do campo, por isso filter[creditLimit]=lots
devolve 400 a indicar o campo. Só os campos que a versão devolve podem ser usados
para filtrar, ordenar ou pesquisar, e um parâmetro desconhecido é recusado em vez de ignorado,
para que um erro de digitação nunca devolva tudo sem aviso. Os parâmetros que começam por
_ são ignorados, para poder contornar caches (cache busting).
Versões
A versão faz parte do caminho, e várias versões do mesmo endpoint podem estar em funcionamento lado a lado. Uma versão publicada nunca muda de forma a quebrar a sua integração: os campos nunca são removidos nem renomeados, e nada passa a ser obrigatório. Da mesma forma, uma versão que chama um Script Module mantém o seu método e os seus parâmetros, e só ganha parâmetros opcionais. Alterações desse tipo chegam numa nova versão.
- As versões Active e Deprecated respondem.
- Uma versão que não existe, ou que ainda está em Draft, devolve
404e lista as versões que existem. - Uma versão Retired devolve
410 Gonee indica aquela para a qual deve migrar.
Todas as respostas indicam que versões existem. Uma versão Deprecated também o indica, bem como quando vai deixar de responder:
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 é o momento em que foi descontinuada (um timestamp Unix),
Sunset a data em que deixa de responder, se o admin a tiver definido, e
Link aponta para a versão seguinte. Registar um aviso no log sempre que
Deprecation aparece é a forma mais simples de saber disso a tempo.
Erros
Uma recusa da Data API é um documento de problema padrão (RFC 7807) cujo
detail explica o que estava errado:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"title": "Forbidden",
"status": 403,
"detail": "None of your roles may Delete on 'customers'."
}
Uma exceção: quando a validação de um Business Event recusa uma escrita, o body do
422 é { "message": "...", "errorColumns": [...] }, que
indica os campos com erro pelos nomes de coluna da própria base de dados.
| Estado | Significado |
|---|---|
400 | O pedido está mal formado: um campo ou parâmetro desconhecido, um valor do tipo errado, um campo obrigatório em falta. |
401 | Falta o token ou a chave, ou o que foi enviado já não é válido. Um utilizador faz refresh e tenta de novo; uma chave de API expirou, foi rodada, ou o seu API client foi desativado ou eliminado. |
403 | Nenhuma das roles da conta pode fazer isto neste endpoint. |
404 | Não existe esse endpoint, versão ou item. |
405 | Esta versão não oferece a operação, por exemplo eliminar, ou um endpoint de Script Module foi chamado com o outro método. |
409 | Os dados estão em conflito: um valor único já existe, ou há outros registos que ainda referem este. |
410 | A versão foi retirada. |
422 | Uma regra de negócio ou uma regra da tabela recusou os valores. |
429 | Demasiados pedidos. Aguarde um pouco e tente de novo. |
500 | O Script Module por trás de um endpoint falhou; detail traz a sua mensagem. |
Descrição OpenAPI
GET /api/data/openapi.json, chamado com o seu token, devolve um documento OpenAPI 3
com exatamente as versões e operações que a sua conta pode usar, com todos os campos e os
respetivos tipos. Importe-o no Postman ou no Insomnia, ou gere a partir dele um cliente tipado.
Volte a obtê-lo depois de o admin publicar uma nova versão.
Exemplos em Python
Com uma chave de API, basta o cabeçalho:
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"])
Com sessão iniciada como utilizador, é preciso gerir a sessão. Este exemplo inicia sessão, faz
refresh um minuto antes de o token expirar, recupera de um 401, lê todas as páginas
e cria um item. Coloque o endereço e as credenciais em variáveis de ambiente.
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")) Ambientes de trial: aí, só a conta do proprietário do trial, e os API clients que o proprietário criar, podem chamar a API.
Para onde ir a seguir
- Business Entities - de onde um endpoint lê, e porque é que uma entidade com um join a uma tabela one-to-many devolve um item por cada linha associada.
- User Management - crie a conta da integração e a respetiva role.