Guia · 15 min

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:

  1. 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.
  2. O endpoint e a versão, por exemplo customers versão 1. Só uma versão com o estado Active ou Deprecated responde; uma versão Draft não.
  3. 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.
  4. 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 de expiresIn segundos, 15 minutos por omissão.
  • refreshToken dá-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.
  • idToken e redirectHost destinam-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 id no 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 um 400; um null conta como omitido.
  • Um módulo que não devolve nada responde 204 No Content. Um que falha responde 500, com a mensagem do próprio módulo em detail.
  • 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á um 405 cujo cabeçalho Allow indica 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âmetroSignificado
pageNúmero da página, a começar em 1.
pageSizeItens 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.
sortUm campo, com - à frente para ordem descendente. Sem ele, os mais recentes primeiro.
searchTexto 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.
OperadorCorresponde a
eq, neIgual, diferente.
gt, gte, lt, lteMaior ou menor que. Números, datas e texto.
like, startswith, endswithTexto que contém o valor, começa ou termina com ele, sem distinguir maiúsculas de minúsculas. Só campos de texto.
inQualquer valor de uma lista separada por vírgulas: filter[id][in]=1,2,3.
nulltrue 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 404 e lista as versões que existem.
  • Uma versão Retired devolve 410 Gone e 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.

EstadoSignificado
400O pedido está mal formado: um campo ou parâmetro desconhecido, um valor do tipo errado, um campo obrigatório em falta.
401Falta 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.
403Nenhuma das roles da conta pode fazer isto neste endpoint.
404Não existe esse endpoint, versão ou item.
405Esta versão não oferece a operação, por exemplo eliminar, ou um endpoint de Script Module foi chamado com o outro método.
409Os dados estão em conflito: um valor único já existe, ou há outros registos que ainda referem este.
410A versão foi retirada.
422Uma regra de negócio ou uma regra da tabela recusou os valores.
429Demasiados pedidos. Aguarde um pouco e tente de novo.
500O 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.