API Pública

A API pública do Associados.app permite que sistemas externos — como um software de ponto de venda (POS) — consultem sócios e liquidem quotas em nome da sua associação. É uma API REST sobre HTTPS, com JSON em ambos os sentidos e autenticação por chave.

URL base

https://associados.app/api/public/v1

Como começar

A API pública está disponível no plano Profissional. Nos restantes planos as chaves não podem ser criadas e os pedidos são recusados com 403 plan_required.

Há duas formas de obter uma chave:

  • Integrações próprias — um administrador cria a chave em Definições → API, escolhendo o nome, as permissões e (se quiser) uma validade.
  • Aplicações parceiras — se está a ligar uma aplicação já integrada com o Associados.app, use o fluxo de ligação. A chave é emitida automaticamente após o administrador dar consentimento, e aparece na mesma lista.

Uma chave dá acesso aos dados de uma única associação — a que a emitiu. Não é preciso identificar a associação nos pedidos: ela é determinada pela chave.

Autenticação

Todos os pedidos exigem uma chave de API, no cabeçalho Authorization ou x-api-key. As chaves começam por assoc_live_.

curl https://associados.app/api/public/v1/associates?search=silva \
  -H "Authorization: Bearer assoc_live_a1b2c3..."

# em alternativa
curl https://associados.app/api/public/v1/associates?search=silva \
  -H "x-api-key: assoc_live_a1b2c3..."

A chave é apresentada uma única vez, no momento em que é criada — guardamos apenas um hash, pelo que não é recuperável depois. Se a perder, é preciso emitir uma nova. Trate-a como uma palavra-passe: mantenha-a no servidor, nunca no código de uma aplicação cliente ou no navegador.

Uma chave pode ser revogada a qualquer momento em Definições → API, com efeito imediato no pedido seguinte. É o que deve fazer se suspeitar que a chave foi exposta.

Um pedido sem chave, ou com uma chave desconhecida, revogada ou expirada, devolve 401 unauthorized.

Âmbitos

Cada chave tem um conjunto de âmbitos (scopes) que delimita o que pode fazer. Um pedido a um endpoint fora dos âmbitos da chave devolve 403 forbidden.

ÂmbitoPermite
associates:readConsultar sócios, fotografias, grupos e quotas em dívida
associates:writeCriar e alterar sócios
quotas:readConsultar quotas, campanhas de pagamento e instituições
quotas:writeLiquidar quotas, reverter liquidações e gerar quotas
transactions:readConsultar pagamentos e recibos
events:readConsultar eventos e inscrições
events:writeCriar e alterar eventos, e inscrever sócios
identity:readConsultar o nome, o logótipo, a cor e o número de sócios da associação

Erros

Os erros usam sempre o mesmo formato. Programe contra o campo code, que é estável — a message é destinada a humanos e pode mudar.

{
  "error": {
    "code": "validation_error",
    "message": "Dados inválidos",
    "issues": [ /* detalhe estruturado, quando o código o traz */ ]
  }
}

issues não é exclusivo dos erros de validação, e a sua forma depende do code: em validation_error é o detalhe por campo, em quota_payment_order é missingQuotaIds, em quota_institution_covered é quotaIds, em registration_blocked é reason e em event_form_answers é fieldErrors, com uma mensagem por pergunta. Leia-o depois de ramificar no código, nunca antes.

CódigoHTTPSignificado
bad_request400O corpo não é JSON
unauthorized401Chave de API em falta, inválida ou revogada
forbidden403A chave não tem a permissão exigida
plan_required403A associação não está no plano Profissional
no_picture404O sócio não tem fotografiaSó em GET /associates/{id}/avatar
not_found404A associação da chave já não existe
deceased_associate409O sócio está registado como falecidoSó em POST /associates/{id}/status
quota_already_paid409A quota já está pagaSó em POST /quotas/{id}/waive
quota_config_missing409A associação não tem configuração de quotasSó em POST /quotas/generate
settlement_in_progress409Outra chamada com esta referência está a decorrerSó em POST /quotas/mark-paid
event_consent_required422O formulário do evento tem um consentimento obrigatório, que só a própria pessoa pode dar; issues.fieldIds diz quaisSó em POST /events/{id}/registrations
event_form_answers422As respostas não servem as perguntas do evento; issues.fieldErrors traz a mensagem de cada campo, pela chave do campoSó em POST /events/{id}/registrations
quota_institution_covered422A quota está a cargo de uma instituição e não é do sócio para perdoarSó em POST /quotas/{id}/waive, POST /quotas/mark-paid
quota_payment_order422A associação exige o pagamento das quotas por ordem e ficaria por pagar uma quota recorrente anterior do mesmo sócio (issues.missingQuotaIds)Só em POST /quotas/mark-paid
registration_blocked422O lugar não pode ser tomado, e issues.reason diz porquê: EVENT_CANCELLED, REGISTRATION_DISABLED, NOT_OPEN_YET, CLOSED, EVENT_PASSED, NOT_ACTIVE, STATUS_BLOCKED, QUOTAS_OVERDUE ou NOT_PUBLICSó em POST /events/{id}/registrations
validation_error422archived fora de true/false
rate_limited429Orçamento de pedidos por minuto esgotado
internal_error500O redimensionamento falhou

Limites

Os pedidos são contados por chave, numa janela de um minuto. Os limites diferem consoante o custo do endpoint:

EndpointsPor minuto
LeiturasGET /meGET /identityGET /associates/{id}/payable-quotasGET /associate-groupsGET /institutionsGET /institutions/{id}GET /transactionsGET /transactions/{id}GET /associatesGET /associates/{id}GET /associates/{id}/quotasGET /quotasGET /quotas/{id}GET /eventsGET /events/{id}GET /events/{id}/registrationsGET /payment-requestsGET /payment-requests/{id}300
Fotografias, que são decodificadas e redimensionadas a cada pedidoGET /associates/{id}/avatar120
EscritasPOST /associatesPATCH /associates/{id}POST /associates/{id}/statusPOST /quotas/{id}/waivePOST /quotas/generatePOST /quotas/mark-paidPOST /quotas/unmark-paidPOST /eventsPATCH /events/{id}POST /events/{id}/registrations60

As respostas bem-sucedidas — e os erros 403 e 429 — trazem X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset (epoch em segundos), para que possa abrandar antes de ser cortado. Um 401 não os traz: sem chave válida não há contador a reportar. Ao exceder o limite recebe 429 rate_limited com Retry-After em segundos — respeite-o.

Um pedido recusado conta na mesma para o limite. Um 403 por falta de permissão não é gratuito: se insistir num endpoint que a chave não pode usar, acabará por receber 429.

Ao repetir um pagamento após um 429, reutilize sempre a mesma externalReference. Um 429 não diz que o pagamento falhou — pode ter sido apenas recusado à entrada. Reutilizar a referência deixa a idempotência resolver o resto. Espere o Retry-After antes de tentar: repetir de imediato pode colidir com o seu próprio pedido ainda em curso e devolver 409 settlement_in_progress.

A chave

GET/mesem permissão exigida300/min

Descrever a chave que fez o pedido

Qual é a associação, o que a chave pode fazer, e quais são os limites por minuto. Não exige permissão nenhuma — é o primeiro pedido que um integrador faz com uma credencial nova.

Resposta 200

tenantobject | null
keyobject
└ namestring | null
└ prefixstring | null
└ scopesstring[]
└ createdAtdata ISO-8601 | null
└ expiresAtdata ISO-8601 | null
tierstring
limitsobject
└ readinteger
└ avatarinteger
└ writeinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Identidade da associação

GET/identityidentity:read300/min

Identidade da associação

Nome, descrição, cor, logótipo e número de sócios ativos — o que a associação já mostra na sua página pública, mais a contagem. O logoUrl abre-se sem credenciais e nunca redireciona.

Resposta 200

namestring
descriptionstring | null
primaryColorstring | null
logoUrlstring | null
activeAssociatesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A associação da chave já não existe
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Sócios

GET/associatesassociates:read300/min

Procurar sócios

A lista nunca carrega fotografias: hasPicture diz se vale a pena pedir /associates/{id}/avatar, que as serve já redimensionadas.

Parâmetros

search (opcional)string
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ associateNumberinteger | null
└ namestring
└ statusstring
└ hasPictureboolean
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
POST/associatesassociates:write60/min

Inscrever um sócio

Idempotente se lhe for dada uma externalReference, e só nesse caso: repetir a chamada com a mesma referência devolve o sócio já criado, com 200 em vez de 201. Sem referência, uma repetição inscreve um duplicado — uma integração não distingue um pedido que falhou de um que demorou.

Corpo

namestring
email (opcional)string | string
admissionDate (opcional)data ISO-8601
photoUploadId (opcional)string
removePhoto (opcional)boolean
address (opcional)string
postalCode (opcional)string
city (opcional)string
birthDate (opcional)data ISO-8601
cityOfBirth (opcional)string
countryOfBirth (opcional)string
maritalStatus (opcional)"SINGLE" | "MARRIED" | "DIVORCED" | "WIDOWED" | "CIVIL_UNION" | "OTHER"
sex (opcional)"MALE" | "FEMALE" | "OTHER"
nationalIdNumber (opcional)string
nationalIdEmissionDate (opcional)data ISO-8601
nationalIdEntity (opcional)string
nationalIdExpirationDate (opcional)data ISO-8601
fiscalId (opcional)string
occupation (opcional)string
phoneNumber (opcional)string
landlinePhone (opcional)string
customFields (opcional)object
notifOptOutEmail (opcional)boolean
notifOptOutSms (opcional)boolean
groupIdstring | null
externalReference (opcional)string

Resposta 200

idstring
associateNumberinteger | null
namestring
emailstring | null
statusstring
admissionDatedata ISO-8601 | null
birthDatedata ISO-8601 | null
phoneNumberstring | null
landlinePhonestring | null
addressstring | null
postalCodestring | null
citystring | null
fiscalIdstring | null
occupationstring | null
groupIdstring | null
customFieldsqualquer | nullCampos personalizados da associação. A forma é definida por cada associação.
hasPictureboolean
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — Repetição idempotente: o sócio que a primeira chamada já tinha criado
  • 201 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A referência já existiu mas o sócio foi entretanto removido
  • 422 — validation_error — Campos inválidos, ou grupo inexistente
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
GET/associates/{id}associates:read300/min

Obter a ficha de um sócio

Não inclui o número do documento de identificação, o registo de óbito, as coordenadas geocodificadas nem as preferências de notificação — quem factura a um sócio não precisa do cartão de cidadão dele.

Parâmetros

idstringno caminho

Resposta 200

idstring
associateNumberinteger | null
namestring
emailstring | null
statusstring
admissionDatedata ISO-8601 | null
birthDatedata ISO-8601 | null
phoneNumberstring | null
landlinePhonestring | null
addressstring | null
postalCodestring | null
citystring | null
fiscalIdstring | null
occupationstring | null
groupIdstring | null
customFieldsqualquer | nullCampos personalizados da associação. A forma é definida por cada associação.
hasPictureboolean
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
PATCH/associates/{id}associates:write60/min

Alterar a ficha de um sócio

Só os campos enviados são escritos. O estado tem endpoint próprio, e a referência externa não é alterável: é a chave de idempotência da criação, e reapontá-la faria uma criação repetida devolver o sócio errado.

Parâmetros

idstringno caminho

Corpo

name (opcional)string
email (opcional)string | null | string
admissionDate (opcional)data ISO-8601 | null
photoUploadId (opcional)string
removePhoto (opcional)boolean
address (opcional)string
postalCode (opcional)string
city (opcional)string
birthDate (opcional)data ISO-8601 | null
cityOfBirth (opcional)string
countryOfBirth (opcional)string
maritalStatus (opcional)"SINGLE" | "MARRIED" | "DIVORCED" | "WIDOWED" | "CIVIL_UNION" | "OTHER"
sex (opcional)"MALE" | "FEMALE" | "OTHER"
nationalIdNumber (opcional)string
nationalIdEmissionDate (opcional)data ISO-8601 | null
nationalIdEntity (opcional)string
nationalIdExpirationDate (opcional)data ISO-8601 | null
fiscalId (opcional)string
occupation (opcional)string
phoneNumber (opcional)string
landlinePhone (opcional)string
customFields (opcional)object
notifOptOutEmail (opcional)boolean
notifOptOutSms (opcional)boolean
groupId (opcional)string | null

Resposta 200

idstring
associateNumberinteger | null
namestring
emailstring | null
statusstring
admissionDatedata ISO-8601 | null
birthDatedata ISO-8601 | null
phoneNumberstring | null
landlinePhonestring | null
addressstring | null
postalCodestring | null
citystring | null
fiscalIdstring | null
occupationstring | null
groupIdstring | null
customFieldsqualquer | nullCampos personalizados da associação. A forma é definida por cada associação.
hasPictureboolean
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação
  • 422 — validation_error — Campos inválidos, ou grupo inexistente
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
GET/associates/{id}/avatarassociates:read120/min

Obter a fotografia de um sócio, redimensionada

Devolve WebP quadrado. size é limitado entre 16 e 256 — um valor fora do intervalo é aproximado, não recusado. Guardar em cache privada apenas: é a fotografia de uma pessoa.

Parâmetros

idstringno caminho
size (opcional)string

Respostas

  • 200 — A fotografia, quadrada, em WebP
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação; no_picture — O sócio não tem fotografia
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — O redimensionamento falhou
GET/associates/{id}/payable-quotasassociates:read300/min

O que um sócio deve neste momento

As quotas a cargo de uma instituição (#366) ficam de fora: deixaram de ser dívida do sócio. /associates/{id}/quotas continua a listá-las, com institutionId preenchido, por isso os totais dos dois endpoints podem divergir de propósito.

Parâmetros

idstringno caminho

Resposta 200

associateobject
└ idstring
└ namestring
└ associateNumberinteger | null
└ statusstring
quotasobject[]
└ idstring
└ kindstring
└ labelstring | null
└ referenceDatedata ISO-8601
└ dueDatedata ISO-8601
└ amountnumber
└ statusstring

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
GET/associates/{id}/quotasquotas:read300/min

Listar as quotas de um sócio

Inclui as quotas a cargo de uma instituição (#366), com institutionId preenchido — ao contrário de /associates/{id}/payable-quotas, que só devolve o que é dívida do sócio.

Parâmetros

idstringno caminho
status (opcional)"PENDING" | "PAID" | "OVERDUE" | "WAIVED" | string
kind (opcional)"RECURRING" | "ONE_OFF" | string
yearinteger
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ associateIdstring
└ associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
└ referenceDatedata ISO-8601
└ dueDatedata ISO-8601
└ amountnumber
└ statusstring
└ kindstring
└ labelstring | null
└ paymentRequestIdstring | null
└ paidAtdata ISO-8601 | null
└ institutionIdstring | nullA instituição que assumiu ou pagou a quota (#366); null quando é do sócio. Uma quota por pagar com instituição não se liquida nem se perdoa pela API — quota_institution_covered — e fica de fora de /associates/{id}/payable-quotas.
└ createdAtdata ISO-8601
└ updatedAtdata ISO-8601
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação
  • 422 — validation_error — status, kind ou year fora dos valores aceites
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
POST/associates/{id}/statusassociates:write60/min

Activar ou desactivar um sócio

Só ACTIVE e INACTIVE. Registar um óbito não se faz por aqui: perdoa quotas, marca uma data e alimenta uma fila que alguém confirma, e nada disso deve acontecer porque um sistema sincronizou uma linha desactualizada.

Parâmetros

idstringno caminho

Corpo

status"ACTIVE" | "INACTIVE"

Resposta 200

idstring
associateNumberinteger | null
namestring
emailstring | null
statusstring
admissionDatedata ISO-8601 | null
birthDatedata ISO-8601 | null
phoneNumberstring | null
landlinePhonestring | null
addressstring | null
postalCodestring | null
citystring | null
fiscalIdstring | null
occupationstring | null
groupIdstring | null
customFieldsqualquer | nullCampos personalizados da associação. A forma é definida por cada associação.
hasPictureboolean
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O sócio não existe nesta associação
  • 409 — deceased_associate — O sócio está registado como falecido
  • 422 — validation_error — status fora de ACTIVE/INACTIVE
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou

Grupos de sócios

GET/associate-groupsassociates:read300/min

Listar grupos de sócios

Resolve o groupId de um sócio. Sem paginação: a lista é pequena e fechada. quotaAmount é a quota própria do grupo, null quando o grupo não altera o valor da associação.

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Quotas

GET/quotasquotas:read300/min

Listar quotas da associação

institutionId traz as quotas que uma instituição assumiu (#366), pagas ou não — o caminho inverso do campo com o mesmo nome em cada quota.

Parâmetros

status (opcional)"PENDING" | "PAID" | "OVERDUE" | "WAIVED" | string
kind (opcional)"RECURRING" | "ONE_OFF" | string
yearinteger
associateId (opcional)string
paymentRequestId (opcional)string
institutionId (opcional)string
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ associateIdstring
└ associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
└ referenceDatedata ISO-8601
└ dueDatedata ISO-8601
└ amountnumber
└ statusstring
└ kindstring
└ labelstring | null
└ paymentRequestIdstring | null
└ paidAtdata ISO-8601 | null
└ institutionIdstring | nullA instituição que assumiu ou pagou a quota (#366); null quando é do sócio. Uma quota por pagar com instituição não se liquida nem se perdoa pela API — quota_institution_covered — e fica de fora de /associates/{id}/payable-quotas.
└ createdAtdata ISO-8601
└ updatedAtdata ISO-8601
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 422 — validation_error — status, kind ou year fora dos valores aceites
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
GET/quotas/{id}quotas:read300/min

Obter uma quota

Parâmetros

idstringno caminho

Resposta 200

idstring
associateIdstring
associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
referenceDatedata ISO-8601
dueDatedata ISO-8601
amountnumber
statusstring
kindstring
labelstring | null
paymentRequestIdstring | null
paidAtdata ISO-8601 | null
institutionIdstring | nullA instituição que assumiu ou pagou a quota (#366); null quando é do sócio. Uma quota por pagar com instituição não se liquida nem se perdoa pela API — quota_institution_covered — e fica de fora de /associates/{id}/payable-quotas.
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A quota não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
POST/quotas/generatequotas:write60/min

Gerar as quotas de um período

Aguenta uma repetição **sequencial** — só cria os períodos em falta, pelo que uma segunda chamada devolve created: 0. Duas chamadas **em paralelo** podem duplicar. Só são emitidas quotas a sócios activos: um id indicado em associateIds que não esteja activo vem devolvido em skippedAssociateIds. Não envia avisos por email: uma chamada pode emitir doze linhas por sócio, e escrever a toda a gente sem ninguém a ver não é um interruptor de máquina.

Corpo

yearinteger
month (opcional)integer
target (opcional)"all" | "missing"
associateIds (opcional)string[]

Resposta 201

createdinteger
skippedAssociateIdsstring[]

Respostas

  • 201 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 409 — quota_config_missing — A associação não tem configuração de quotas
  • 422 — validation_error — Campos inválidos
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
POST/quotas/mark-paidquotas:write60/min

Liquidar quotas

Idempotente por externalReference. Uma repetição devolve o resultado original, com os mesmos números de recibo — é o que permite a quem cobrou no terreno e perdeu a ligação reimprimir o recibo em vez de emitir um segundo pelo mesmo dinheiro.

Corpo

quotaIdsstring[]
paymentMethod (opcional)string
externalReferencestring

Resposta 200

idempotentboolean
statusstring
paidinteger
quotaIdsstring[]
externalReferencestring
transactions (opcional)object[]
└ idstring
└ quotaIdstring
└ receiptNumberstring | null
└ amountnumber

Respostas

  • 200 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 409 — settlement_in_progress — Outra chamada com esta referência está a decorrer
  • 422 — quota_payment_order — A associação exige o pagamento das quotas por ordem e ficaria por pagar uma quota recorrente anterior do mesmo sócio (issues.missingQuotaIds); quota_institution_covered — Uma das quotas está a cargo de uma instituição e não pode ser paga pelo sócio (issues.quotaIds). Nada foi registado; validation_error — Campos inválidos
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
POST/quotas/unmark-paidquotas:write60/min

Reverter uma liquidação

Só reverte liquidações feitas por esta API. O dinheiro recebido à porta pela aplicação de terreno partilha o mesmo livro de movimentos e não é reversível daqui: dinheiro entregue em mão só pode ser desfeito por quem o possa devolver.

Corpo

externalReferencestring

Resposta 200

reversedboolean
alreadyReversedboolean
quotaIdsstring[]

Respostas

  • 200 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — Não há liquidação desta API com essa referência
  • 422 — validation_error — Campos inválidos
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
POST/quotas/{id}/waivequotas:write60/min

Perdoar uma quota

Perdoar não é liquidar: não há movimento, recibo nem fatura. Uma quota já paga é recusada — reverter uma liquidação é /quotas/unmark-paid, que também reverte o movimento.

Parâmetros

idstringno caminho

Resposta 200

idstring
associateIdstring
associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
referenceDatedata ISO-8601
dueDatedata ISO-8601
amountnumber
statusstring
kindstring
labelstring | null
paymentRequestIdstring | null
paidAtdata ISO-8601 | null
institutionIdstring | nullA instituição que assumiu ou pagou a quota (#366); null quando é do sócio. Uma quota por pagar com instituição não se liquida nem se perdoa pela API — quota_institution_covered — e fica de fora de /associates/{id}/payable-quotas.
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A quota não existe nesta associação
  • 409 — quota_already_paid — A quota já está paga
  • 422 — quota_institution_covered — A quota está a cargo de uma instituição e não é do sócio para perdoar
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou

Instituições

GET/institutionsquotas:read300/min

Listar instituições

Resolve o institutionId das quotas e dos webhooks quota.paid. Por omissão só traz as instituições activas; archived=true inclui as arquivadas. As notas internas e o controlo de facturação não são publicados.

Parâmetros

search (opcional)string
archived (opcional)"true" | "false"
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ namestring
└ fiscalIdstring
└ addressstring
└ postalCodestring
└ citystring
└ countrystring
└ defaultEmailstring | null
└ phonestring | null
└ reviewedAtdata ISO-8601 | nullNull significa que foi um participante que a criou a partir da página de pagamento e ainda ninguém da associação a verificou.
└ archivedAtdata ISO-8601 | null
└ createdAtdata ISO-8601
└ updatedAtdata ISO-8601
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 422 — validation_error — archived fora de true/false
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
GET/institutions/{id}quotas:read300/min

Obter uma instituição

Uma instituição arquivada devolve-se na mesma, com archivedAt preenchido: as quotas que ela pagou continuam a apontar-lhe.

Parâmetros

idstringno caminho

Resposta 200

idstring
namestring
fiscalIdstring
addressstring
postalCodestring
citystring
countrystring
defaultEmailstring | null
phonestring | null
reviewedAtdata ISO-8601 | nullNull significa que foi um participante que a criou a partir da página de pagamento e ainda ninguém da associação a verificou.
archivedAtdata ISO-8601 | null
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A instituição não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Campanhas de pagamento

GET/payment-requestsquotas:read300/min

Listar campanhas de pagamento pontual

Resposta 200

dataobject[]
└ idstring
└ namestring
└ kind"CAMPAIGN" | "JOINING_FEE"CAMPAIGN: criada por um administrador para um conjunto de sócios. JOINING_FEE: a jóia da associação, à qual cada sócio novo é acrescentado; o amount é o valor actual da jóia.
└ amountnumber
└ totalinteger
└ paidinteger
└ pendinginteger
└ createdAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
GET/payment-requests/{id}quotas:read300/min

Obter uma campanha de pagamento e as suas quotas

Parâmetros

idstringno caminho

Resposta 200

idstring
namestring
kind"CAMPAIGN" | "JOINING_FEE"CAMPAIGN: criada por um administrador para um conjunto de sócios. JOINING_FEE: a jóia da associação, à qual cada sócio novo é acrescentado; o amount é o valor actual da jóia.
amountnumber
createdAtdata ISO-8601
updatedAtdata ISO-8601
quotasobject[]
└ idstring
└ associateIdstring
└ associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
└ referenceDatedata ISO-8601
└ dueDatedata ISO-8601
└ amountnumber
└ statusstring
└ kindstring
└ labelstring | null
└ paymentRequestIdstring | null
└ paidAtdata ISO-8601 | null
└ institutionIdstring | nullA instituição que assumiu ou pagou a quota (#366); null quando é do sócio. Uma quota por pagar com instituição não se liquida nem se perdoa pela API — quota_institution_covered — e fica de fora de /associates/{id}/payable-quotas.
└ createdAtdata ISO-8601
└ updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — A campanha não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Movimentos

GET/transactionstransactions:read300/min

Listar movimentos

As datas filtram por quando o dinheiro mudou de mãos, não por quando o registo foi criado — um pagamento recebido no terreno e sincronizado mais tarde conta no dia em que foi cobrado.

Parâmetros

associateId (opcional)string
from (opcional)string
to (opcional)string
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ associateIdstring | null
└ associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
└ quotaIdstring | null
└ typestring
└ amountnumber
└ descriptionstring | null
└ paymentMethodstring | null
└ receiptNumberstring | null
└ externalReferencestring | null
└ collectedAtdata ISO-8601 | null
└ createdAtdata ISO-8601
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 422 — validation_error — Datas inválidas, ou from posterior a to
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
GET/transactions/{id}transactions:read300/min

Obter um movimento

O transactionId que vem numa liquidação e no webhook quota.paid resolve-se aqui, sem percorrer a lista.

Parâmetros

idstringno caminho

Resposta 200

idstring
associateIdstring | null
associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
quotaIdstring | null
typestring
amountnumber
descriptionstring | null
paymentMethodstring | null
receiptNumberstring | null
externalReferencestring | null
collectedAtdata ISO-8601 | null
createdAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O movimento não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado

Eventos

GET/eventsevents:read300/min

Listar eventos

isPublic filtra pelo que a página pública da associação mostra, não por visibilidade perante quem chama: uma chave da associação já está dentro do perímetro de confiança. Sem o filtro vêm os dois.

Parâmetros

from (opcional)string
to (opcional)string
isPublic (opcional)"true" | "false"
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ titlestring
└ descriptionstring | null
└ datedata ISO-8601
└ endDatedata ISO-8601 | null
└ locationstring | null
└ isPublicboolean
└ status"SCHEDULED" | "CANCELLED"
└ coverImageUrlstring | null
└ createdAtdata ISO-8601
└ updatedAtdata ISO-8601
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 422 — validation_error — Datas inválidas, from posterior a to, ou isPublic fora de true/false
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
POST/eventsevents:write60/min

Criar um evento

A imagem de capa não é aceite: é um data URL em base64 numa coluna de texto, e nada impediria um chamador não vigiado de enviar megabytes por evento.

Corpo

titlestring
description (opcional)string
datedata ISO-8601 | stringData ISO-8601, com ou sem hora (2026-05-01T09:00:00Z ou 2026-05-01).
endDate (opcional)data ISO-8601 | stringData ISO-8601, com ou sem hora (2026-05-01T09:00:00Z ou 2026-05-01).
location (opcional)string
isPublic (opcional)boolean

Resposta 201

idstring
titlestring
descriptionstring | null
datedata ISO-8601
endDatedata ISO-8601 | null
locationstring | null
isPublicboolean
status"SCHEDULED" | "CANCELLED"
coverImageUrlstring | null
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 201 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 422 — validation_error — Campos inválidos, ou endDate anterior a date
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
GET/events/{id}events:read300/min

Obter um evento

Parâmetros

idstringno caminho

Resposta 200

idstring
titlestring
descriptionstring | null
datedata ISO-8601
endDatedata ISO-8601 | null
locationstring | null
isPublicboolean
status"SCHEDULED" | "CANCELLED"
coverImageUrlstring | null
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O evento não existe nesta associação
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
PATCH/events/{id}events:write60/min

Alterar um evento

Parâmetros

idstringno caminho

Corpo

title (opcional)string
description (opcional)string
date (opcional)data ISO-8601 | stringData ISO-8601, com ou sem hora (2026-05-01T09:00:00Z ou 2026-05-01).
endDate (opcional)data ISO-8601 | stringData ISO-8601, com ou sem hora (2026-05-01T09:00:00Z ou 2026-05-01).
location (opcional)string
isPublic (opcional)boolean

Resposta 200

idstring
titlestring
descriptionstring | null
datedata ISO-8601
endDatedata ISO-8601 | null
locationstring | null
isPublicboolean
status"SCHEDULED" | "CANCELLED"
coverImageUrlstring | null
createdAtdata ISO-8601
updatedAtdata ISO-8601

Respostas

  • 200 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O evento não existe nesta associação
  • 422 — validation_error — Campos inválidos, ou endDate anterior a date
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou
GET/events/{id}/registrationsevents:read300/min

Listar inscrições num evento

Por ordem de inscrição, que é a ordem por que a lista de espera é servida. Inclui as inscrições canceladas — filtre por status para as deixar de fora. order só vem preenchido num evento pago, e diz quanto custa o lugar e se já está pago; o endereço de pagamento vai por email para o sócio.

Parâmetros

idstringno caminho
status (opcional)"CONFIRMED" | "WAITLISTED" | "CANCELLED" | string
page (opcional)string
pageSize (opcional)string

Resposta 200

dataobject[]
└ idstring
└ eventIdstring
└ associateIdstring | null
└ associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
└ guestNamestring | null
└ guestEmailstring | null
└ guestPhonestring | null
└ status"CONFIRMED" | "WAITLISTED" | "CANCELLED"WAITLISTED é um lugar pedido num evento cheio: ainda não é um lugar, e passa a CONFIRMED quando um se liberta.
└ registeredAtdata ISO-8601
└ cancelledAtdata ISO-8601 | null
└ order (opcional)object | nullQuanto custa o lugar e se já está pago. O endereço de pagamento não vem aqui: é uma credencial para rotas que também escrevem, e o sócio recebe-a por email ao inscrever-se.
pageinteger
pageSizeinteger
totalinteger
totalPagesinteger

Respostas

  • 200 — OK
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O evento não existe nesta associação
  • 422 — validation_error — status fora dos valores aceites
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
POST/events/{id}/registrationsevents:write60/min

Inscrever um sócio num evento

As regras do sócio aplicam-se na mesma: um evento cheio devolve um lugar WAITLISTED, não um erro. Repetir a inscrição de quem já tem lugar devolve esse lugar com **200**, sem criar um segundo. Um evento cujo formulário tem um consentimento obrigatório é recusado com event_consent_required: esse consentimento é do sócio, dá-o ele na página da associação, e uma inscrição por chave só o poderia inventar ou deixar em branco.

Parâmetros

idstringno caminho

Corpo

associateIdstring
answers (opcional)object[]
└ fieldIdstring
└ value (opcional)string
└ values (opcional)string[]

Resposta 200

idstring
eventIdstring
associateIdstring | null
associate (opcional)object
└ idstring
└ namestring
└ associateNumberinteger | null
guestNamestring | null
guestEmailstring | null
guestPhonestring | null
status"CONFIRMED" | "WAITLISTED" | "CANCELLED"WAITLISTED é um lugar pedido num evento cheio: ainda não é um lugar, e passa a CONFIRMED quando um se liberta.
registeredAtdata ISO-8601
cancelledAtdata ISO-8601 | null
order (opcional)object | nullQuanto custa o lugar e se já está pago. O endereço de pagamento não vem aqui: é uma credencial para rotas que também escrevem, e o sócio recebe-a por email ao inscrever-se.

Respostas

  • 200 — O sócio já tinha lugar; devolve-se o que tem
  • 201 — OK
  • 400 — bad_request — O corpo não é JSON
  • 401 — unauthorized — Chave de API em falta, inválida ou revogada
  • 403 — plan_required — A associação não está no plano Profissional; forbidden — A chave não tem a permissão exigida
  • 404 — not_found — O evento ou o sócio não existem nesta associação
  • 422 — registration_blocked — O lugar não pode ser tomado, e issues.reason diz porquê: EVENT_CANCELLED, REGISTRATION_DISABLED, NOT_OPEN_YET, CLOSED, EVENT_PASSED, NOT_ACTIVE, STATUS_BLOCKED, QUOTAS_OVERDUE ou NOT_PUBLIC; event_consent_required — O formulário do evento tem um consentimento obrigatório, que só a própria pessoa pode dar; issues.fieldIds diz quais; event_form_answers — As respostas não servem as perguntas do evento; issues.fieldErrors traz a mensagem de cada campo, pela chave do campo; validation_error — Campos inválidos
  • 429 — rate_limited — Orçamento de pedidos por minuto esgotado
  • 500 — internal_error — A escrita falhou

Idempotência

Pagamentos não podem ser cobrados duas vezes por causa de uma rede instável. Por isso /quotas/mark-paid exige uma externalReference: um identificador estável e único do seu lado para aquele pagamento — tipicamente o id da linha de encomenda. Não use um valor aleatório gerado a cada tentativa, ou a proteção deixa de funcionar.

Com essa referência, pode repetir o pedido à vontade:

  • Se a liquidação já tinha terminado, a resposta original é devolvida tal e qual, com idempotent: true. Nada é pago de novo.
  • Se ainda estiver a decorrer (o seu pedido anterior ainda não respondeu), recebe 409 settlement_in_progress. Aguarde um momento e repita.
  • Se a tentativa anterior falhou a meio, a repetição liquida normalmente.

Perante um timeout ou um 500, a atitude correta é repetir o pedido com a mesma referência — nunca com uma nova.

Webhooks

Em vez de sondar a API, pode receber um POST quando algo acontece na associação. O endpoint é registado de duas maneiras: pela própria associação, em Definições → Webhooks, ou pelo fluxo de ligação. Em ambos os casos é devolvido o segredo de assinatura (começa por whsec_) uma única vez — guardamo-lo cifrado e não há forma de o voltar a ler.

Nem todos os endereços são aceites — veja endereços aceites.

EventoQuando
payment_request.createdFoi criada uma campanha de pagamento pontual
payment_request.updatedO nome ou o valor de um pagamento pontual mudou (a jóia)
payment_request.closedUma campanha de pagamento foi encerrada
quota.paidUma quota foi liquidada, seja qual for a origem
associate.createdUm sócio foi inscrito
associate.updatedA ficha de um sócio foi alterada
associate.status_changedUm sócio foi activado ou desactivado
quota.generatedForam geradas quotas para um período
quota.waivedUma quota foi perdoada
event.createdFoi criado um evento
event.updatedUm evento foi alterado
event.registration_createdAlguém se inscreveu num evento
event.registration_cancelledUma inscrição num evento foi cancelada
event.cancelledUm evento foi cancelado

quota.paid inclui source (admin, api ou stripe) e externalReference, para que possa distinguir os pagamentos que o seu sistema registou dos restantes e ignorar o eco dos seus próprios lançamentos. Os restantes campos são quotaId, associateId, transactionId, amount, kind, paymentMethod, receiptNumber, paidAt e institutionId — a instituição cuja transferência pagou a quota, ou null quando foi o sócio a pagá-la.

O pedido

POST https://o-seu-servidor.pt/webhooks/associados
X-Webhook-Event: payment_request.created
X-Webhook-Delivery: clxd8e2...
X-Webhook-Signature: sha256=3f9a1c...

{
  "id": "clxd8e2...",
  "event": "payment_request.created",
  "createdAt": "2026-02-10T09:12:00.000Z",
  "data": { /* específico do evento */ }
}

Verificar a assinatura

X-Webhook-Signature é sha256= seguido do HMAC-SHA256 do corpo em bruto do pedido, com o seu segredo como chave. Calcule o HMAC antes de fazer parse do JSON — se reserializar o objeto, a assinatura deixa de bater certo. Rejeite qualquer pedido cuja assinatura não confira.

import crypto from "crypto";

const esperado =
  "sha256=" +
  crypto.createHmac("sha256", WEBHOOK_SECRET).update(corpoEmBruto).digest("hex");

const valido = crypto.timingSafeEqual(
  Buffer.from(esperado),
  Buffer.from(req.headers["x-webhook-signature"]),
);

Repetições

Responda 2xx para confirmar a receção. Qualquer outra resposta — ou não responder em 10 segundos — conta como falha e o envio é repetido, até 6 tentativas, com espera exponencial a começar em 1 minuto e limitada a 6 horas.

Como há repetições, o mesmo evento pode chegar mais do que uma vez. Use o id da entrega (também em X-Webhook-Delivery) para ignorar duplicados. Responda depressa e processe em segundo plano.

Endereços aceites

Quem entrega os eventos é um cliente HTTP que corre dentro da nossa rede, por isso um endereço de webhook é uma instrução para irmos buscar alguma coisa a algum lado. Não aceitamos qualquer um. Se vir URL não permitido numa entrega, ou uma recusa ao guardar, é uma destas regras:

  • Só https, e só no porto 443. Um porto arbitrário é como se chega a um painel interno.
  • Sem credenciais no URL — nada de https://utilizador:senha@….
  • Tem de resolver para um endereço público. Ficam de fora localhost, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 100.64.0.0/10, os intervalos de documentação e reservados, e os equivalentes em IPv6 — incluindo um IPv4 embrulhado em IPv6, como ::ffff:127.0.0.1.
  • Nomes reservados para a rede local — .local, .internal, .lan, .home.arpa — não são aceites.

A verificação corre ao guardar e outra vez antes de cada envio, e a ligação é feita para um dos endereços que a verificação aprovou. Verificar só ao guardar não chegaria: um nome pode ser reapontado a seguir, e é exactamente isso que alguém tentaria.

Não seguimos redirecionamentos. Uma resposta 3xx conta como entrega falhada — o endereço para onde aponta não passou por verificação nenhuma. Aponte o webhook directamente para o destino final.

Alterar o endereço de um endpoint gera um segredo novo e invalida o anterior: um endpoint reapontado é uma relação de confiança nova, e o destino antigo deixa de conseguir verificar as nossas assinaturas.

Ligar uma aplicação

As aplicações parceiras obtêm a chave através de um fluxo de consentimento, em vez de a chave ser copiada à mão. É um authorization code simplificado:

  1. A aplicação envia o administrador para /connect/authorize, com client_id, redirect_uri e, opcionalmente, state, scope e webhook_url.
  2. O administrador autentica-se, escolhe a associação a ligar e vê os acessos pedidos. Ao autorizar, é reencaminhado para a redirect_uri com um code de utilização única, válido por 5 minutos.
  3. A aplicação troca esse código, servidor-a-servidor, em POST /api/connect/token, autenticando-se com client_id e client_secret. A resposta traz a chave de API e, se tiver sido pedido um webhook_url, o segredo de assinatura do webhook.
POST /api/connect/token
{
  "code": "ac_...",
  "client_id": "a-sua-app",
  "client_secret": "...",
  "redirect_uri": "https://a-sua-app.pt/callback"
}

{
  "api_key": "assoc_live_...",
  "scopes": "associates:read,quotas:read,quotas:write",
  "tenant": { "id": "...", "name": "Associação Exemplo" },
  "webhook": { "id": "...", "secret": "whsec_...", "events": "..." },
  "base_url": "https://associados.app"
}

A chave nunca viaja num reencaminhamento do navegador — só o código de utilização única — para não ficar em históricos e registos de servidor.

Não há registo dinâmico de clientes: cada aplicação parceira é pré-registada por nós, com o seu segredo e a lista exata de redirect_uri permitidas. Para registar uma aplicação, fale connosco.

Especificação

Toda a referência acima é gerada a partir das próprias rotas, e a mesma origem serve um documento OpenAPI 3.1:

GET https://associados.app/api/public/v1/openapi.json

Não exige chave — uma especificação é o que se lê antes de ter uma credencial, e descreve formatos, não dados de nenhuma associação. Serve para gerar um cliente na sua linguagem, para importar num Postman ou num Insomnia, e para comparar contra o que o seu código espera.

Porque é gerado, não pode divergir do que a API faz: um endpoint que exista sem estar declarado faz a nossa suite de testes falhar. Cada operação traz também o âmbito que exige e o orçamento de pedidos por minuto que gasta.

Precisa de ajuda a integrar?

Diga-nos o que está a construir e ajudamos a encontrar o caminho mais curto.