Blastack

Programadores

A API e as três ferramentas com que se liga

Dezassete pontos de acesso em dez caminhos, cinco eventos de saída e uma especificação OpenAPI 3.1 pública que o Make e o n8n importam sozinhos. Está aqui tudo: cada parâmetro um a um, exemplos que funcionam tal como estão e a lista do que não faz.

Cole o primeiro endereço no importador do n8n ou do Make e os nós aparecem sozinhos. O Zapier não lê OpenAPI: é precisamente por isso que os seus passos estão escritos à parte, mais abaixo.

Num relance

O que é preciso para a primeira chamada

Oito dados. Com estes escreve-se o pedido sem ler mais nada.

O quêValorNota
Raiz da APIhttps://app.blastack.com/api/v1Vive na aplicação, não neste sítio comercial. Um só endereço, e não muda.
Especificaçãohttps://app.blastack.com/api/openapi.jsonPública e sem chave, para que um importador a possa ler antes sequer de haver credenciais.
AutenticaçãoAuthorization: Bearer blsk_<prefix>_<secret>No cabeçalho, nunca no endereço. Os endereços acabam no registo do servidor, no do proxy à frente e no histórico de quem a experimentou à mão.
Formatoapplication/json; charset=utf-8JSON à ida e à vinda. Não há XML nem formulários.
Limite120 req/min · RateLimit-RemainingPor chave e por minuto. Cada resposta diz quantos faltam e quando a contagem recomeça.
Paginaçãopage=1 · per_page=50 · ≤ 200A primeira página é a 1, não a 0. Pedir mais de duzentos é cortado em vez de recusado.
Erros{ error: { status, code, message } }Sempre o mesmo objeto, com um código estável pensado para lhe escrever uma condição por cima.
Ambiente de testes—Não existe. Trabalha-se contra a produção, e é por isso que compensa começar com uma chave só de leitura.
curl https://app.blastack.com/api/v1/people?per_page=1 \
  -H "Authorization: Bearer blsk_a1b2c3d4e5f6_YOUR_SECRET"

O que há

Três superfícies, e nenhuma a meio

As três funcionam. O que falta está mais abaixo, na sua própria secção e dito pelo nome.

Aprovisionamento SCIM 2.0

Para que o vosso sistema de pessoal crie as pessoas, as mude de departamento e as desative sem ninguém copiar nada à mão. É a metade que interessa mesmo a um departamento de informática: no dia em que dispensam alguém, a conta desativa-se sozinha.

Filtro por userName e externalId, paginação e alteração parcial. Lêem-se o departamento e a morada de trabalho, por isso quem entra por aqui nasce com o departamento e o centro de trabalho já preenchidos. A saída desativa, não apaga.

Início de sessão único (OpenID Connect)

Entrar com a conta da empresa a partir do Entra ID, do Google Workspace ou do Okta. Convive com o código de adesão, que é como entra a parte do quadro de pessoal sem correio da empresa.

Configura-se colando o endereço de descoberta do fornecedor e é verificada contra ele antes de guardar. Os domínios de correio verificam-se por DNS. Não há SAML nem LDAP.

API da Blastack

Publicar a partir do programa que já sabe que a linha 3 para na quinta-feira, desativar quem sai do sistema de pessoal, e sermos nós a ligar quando acontece algo em vez de terem de perguntar de cinco em cinco minutos.

JSON sobre HTTPS, chave de API no cabeçalho, âmbitos por chave, erros com código estável e eventos de saída assinados. É o que documenta o resto desta página.

Automatização

Zapier, Make e n8n, passo a passo

As três não se montam da mesma maneira, e dizê-lo poupa a tarde que se perde a descobri-lo. O Make e o n8n leem a especificação e configuram-se quase sozinhos; o Zapier não a lê, por isso ali cada chamada escreve-se à mão. As três funcionam; o trabalho não é o mesmo.

FerramentaLê a especificação?Disparador recomendado
n8nSim, inteiraNó «Webhook» + POST /webhooks
MakeSim, no editor de aplicações próprias«Custom webhook» + POST /webhooks
ZapierNão«Catch Hook» + POST /webhooks

n8n

O caminho mais curto dos três. Cria-se uma credencial de cabeçalho, cola-se o endereço da especificação no importador e fica um nó por operação com os parâmetros já postos.

Passos

  1. 01Credenciais → nova → «Header Auth». O nome do cabeçalho é Authorization e o valor é Bearer, um espaço e a vossa chave.
  2. 02Nó «HTTP Request», autenticação «Generic Credential Type» → «Header Auth» e depois a credencial acabada de criar. Método e caminho saem da tabela de pontos de acesso.
  3. 03Para não escrever os parâmetros à mão, deem ao importador o endereço da especificação: descreve cada ponto de acesso com os valores admitidos, por isso o que é gerado já vem com listas em vez de caixas de texto livre.
  4. 04Para disparar: nó «Webhook», copiem o seu endereço de produção e registem-no com POST /webhooks escolhendo os eventos. Perguntar de cinco em cinco minutos deixa de ser preciso.

A ter em conta

O nó de webhook responde 200 antes de executar o resto do fluxo, que é exatamente o que faz falta aqui: ao fim de dez segundos desistimos e isso conta como falha.

Make

O módulo genérico de HTTP chega para tudo, e quem for montar muitos cenários pode carregar a especificação no editor de aplicações próprias e ficar com um módulo por operação.

Passos

  1. 01Módulo «HTTP» → «Make an API Key Auth request». Na ligação, a chave chama-se Authorization, o valor é Bearer mais a vossa chave, e vai no cabeçalho.
  2. 02Método, caminho e corpo saem da tabela de pontos de acesso e dos exemplos mais abaixo. O tipo de conteúdo é application/json.
  3. 03Se forem montar vários cenários, o editor de aplicações próprias aceita a especificação e gera um módulo por operação: menos cenários escritos à mão e menos gralhas nos nomes dos filtros.
  4. 04Para disparar: módulo «Webhooks» → «Custom webhook», copiem o endereço e registem-no com POST /webhooks. O Make responde de imediato e processa depois, que é a ordem certa aqui.

A ter em conta

O Make cobra à operação: um cenário que pergunte de cinco em cinco minutos queima oito mil operações por mês para não encontrar quase nada. Com o webhook gasta-se uma por evento.

Zapier

O Zapier não importa OpenAPI. Monta-se com os passos genéricos de webhooks, ou construindo uma integração privada na plataforma de programadores deles, onde cada ponto de acesso se declara à mão.

Passos

  1. 01Disparador: «Webhooks by Zapier» → «Catch Hook». Copiem o endereço que vos der e registem-no com POST /webhooks escolhendo os eventos.
  2. 02Ação: «Webhooks by Zapier» → «Custom Request». Método e endereço da tabela, cabeçalhos Authorization com Bearer mais a vossa chave e Content-Type com application/json, e o JSON no campo dos dados.
  3. 03Acrescentem um passo «Code by Zapier» que verifique a assinatura antes de fazer seja o que for. O Zapier não a verifica por vocês e o endereço do disparador é público: sem esse passo, qualquer pessoa que o descubra pode inventar uma paragem de linha.
  4. 04Se preferirem nós com nome em vez de pedidos genéricos, é preciso construir uma integração privada e declarar ali cada ponto de acesso com os seus campos. É mais trabalho, e a especificação não poupa nada disso.

A ter em conta

Os disparadores por sondagem gastam uma tarefa em cada passagem, encontrem alguma coisa ou não. O «Catch Hook» só gasta quando aconteceu mesmo algo.

Chaves

Uma chave por integração, com o estritamente necessário

Hoje emitimo-las nós, à mão, e dizemos com que âmbitos. Não há ecrã de autosserviço e está dito mais abaixo, entre o que falta. Cada chave está presa a uma empresa e nenhuma rota lê um identificador de empresa do pedido: não há onde enganar-se.

  1. 01

    Peçam-nos a chave

    Digam-nos para que é e o que tem de poder fazer, e emitimo-la com esses âmbitos e mais nenhum. Mostra-se uma só vez: dela guardamos apenas um resumo, por isso uma chave perdida revoga-se e emite-se outra.

  2. 02

    Mandem-na em cada pedido

    No cabeçalho de autorização, nunca no endereço. Os endereços acabam no registo do servidor, no do proxy à frente e no histórico de quem a experimentou à mão.

  3. 03

    Guardem-na onde guardam as outras

    No vosso gestor de segredos, não no repositório. Começa por um prefixo reconhecível precisamente para que os detetores de segredos a apanhem se escapar.

A API entra no módulo de identidade corporativa, o mesmo que abre o início de sessão único e o aprovisionamento. Se a empresa não o tiver contratado, a resposta di-lo com essas palavras e não com um erro genérico.

Os âmbitos, um a um

ÂmbitoO que abre
people:readLer o quadro de pessoal e os departamentos e centros de trabalho a que se pode dirigir um comunicado. É tudo o que um painel precisa.
people:writeCriar, alterar e desativar pessoas. Desativar fecha-lhes as sessões, por isso não vem de oferta com o de leitura.
announcements:readLer o que foi publicado e quem confirmou uma leitura obrigatória. Está à parte do das pessoas: uma lista de nomes e funções é o diretório, e o texto dos comunicados não.
announcements:writePublicar no quadro de avisos. Respeita a fila de revisão da empresa, por isso uma chave não salta o circuito de aprovação de ninguém.
metrics:readOs indicadores agregados. Aqui não sai o nome de ninguém.
webhooks:readVer os endereços registados e o registo de entregas. É o que responde a «porque é que não chega nada» sem abrir um pedido de apoio.
webhooks:writeRegistar, alterar e apagar endereços de evento de saída. O mais delicado da lista: decide para que servidor da internet mandamos o conteúdo dos vossos comunicados.

Pontos de acesso

Dezassete operações em dez caminhos

Agrupadas por família. A coluna do âmbito é a que interessa ver ao pedir a chave: se faltar, a resposta diz qual era preciso e quais tem a vossa.

MétodoCaminhoÂmbitoO que faz
GET/peoplepeople:readO quadro de pessoal, com o departamento, o centro de trabalho e o identificador que cada pessoa tem no vosso sistema de pessoal. Dezassete parâmetros, entre eles o filtro pela data da última alteração, que é o que permite sincronizar de quinze em quinze minutos sem trazer tudo.
GET/people/{id}people:readUma pessoa concreta, procurada pelo nosso identificador ou pelo vosso. Serve para atualizar uma ficha sem percorrer a lista toda.
POST/peoplepeople:writeCria alguém. É para quando o vosso sistema de pessoal não fala SCIM; se falar, melhor SCIM, que também trata das saídas. O departamento e o centro de trabalho indicam-se pelo nome e criam-se se não existirem.
PATCH/people/{id}people:writeMuda só os campos que enviarem. Um que fique de fora não é tocado: uma sincronização que só conhece a função não pode apagar o centro de trabalho a ninguém.
DELETE/people/{id}people:writeDesativa; nunca apaga. As sessões fecham-se de imediato, por isso quem sai numa sexta-feira fica fora nessa sexta-feira. O que publicou fica.
GET/audiencespeople:readOs departamentos e os centros de trabalho que existem, com quantas pessoas há em cada um. É daqui que saem os nomes a que se pode dirigir um comunicado, e o filtro dos grupos vazios é o que encontra um mapeamento de aprovisionamento partido.
GET/announcementsannouncements:readTudo o que foi publicado, com as leituras e as confirmações de cada peça. Inclui rascunhos e recusados para que um painel possa desenhar o funil inteiro.
GET/announcements/{id}announcements:readUma publicação concreta. É o passo seguinte ao identificador que uma publicação devolve ou que chega num evento de saída.
POST/announcementsannouncements:writePublica no quadro de avisos. É preciso dizer quem assina e não há valor por omissão. Se essa pessoa não puder publicar sem revisão, o comunicado entra na fila e a resposta di-lo.
GET/announcements/{id}/acknowledgementsannouncements:readQuem confirmou uma leitura obrigatória e quem não, com nome e hora. É a única lista nominal de toda a API, e é-o porque esse é justamente o sentido da leitura obrigatória: um ato voluntário, assinado e datado que se pode mostrar a uma inspeção.
GET/metricsmetrics:readO quadro de pessoal de hoje, as entradas, as publicações e o alcance por publicação e por departamento. Os mesmos números do ecrã de análise, calculados pelo mesmo código.
GET/webhookswebhooks:readOs vossos endereços registados, cada um com a sua saúde: falhas seguidas, o que o vosso servidor respondeu da última vez e, se o tivermos pausado, porquê.
POST/webhookswebhooks:writeRegista um endereço e os eventos que quer. A resposta traz o segredo de assinatura, e é a única vez em que se vê.
GET/webhooks/{id}webhooks:readUm endereço concreto, com a sua saúde.
PATCH/webhooks/{id}webhooks:writeMuda o que enviarem. Pôr o endereço outra vez ativo levanta também a pausa que tenhamos aplicado depois de falhas repetidas e põe o contador a zero.
DELETE/webhooks/{id}webhooks:writeApaga-o com o seu histórico. Para deixar de receber durante um tempo sem perder o histórico, é melhor desativá-lo.
GET/webhooks/{id}/deliverieswebhooks:readTudo o que tentámos mandar-vos: tentativas, o que responderam e quando é a próxima. O identificador é o mesmo que viajou no cabeçalho, por isso cruza-se com os vossos registos.

Parâmetros

Todos, ponto de acesso a ponto de acesso

Nenhuma lista aqui está resumida: são as mesmas que o servidor verifica. Um parâmetro que não esteja aqui não existe, e escrevê-lo devolve um erro que diz quais admite esse ponto de acesso em vez de o ignorar em silêncio.

A coluna da direita está em inglês de propósito. Sai do catálogo de onde também se gera a especificação, por isso é palavra por palavra o que o n8n e o Make mostram dentro dos seus nós. Traduzi-la aqui seria ter duas versões que um dia diriam coisas diferentes.

GET /people

people:read

O quadro de pessoal, com o departamento, o centro de trabalho e o identificador que cada pessoa tem no vosso sistema de pessoal. Dezassete parâmetros, entre eles o filtro pela data da última alteração, que é o que permite sincronizar de quinze em quinze minutos sem trazer tudo.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
pageinteiromín. 112Page to return. The first page is 1, not 0.
per_pageinteiromín. 1 · máx. 200 · fora do intervalo é cortado em vez de falhar50100How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.
sorttextocreated_at · updated_at · name · emailcreated_atField to sort by. Defaults to created_at.
ordertextoasc · descascSort direction.
fieldstexto · vários separados por vírgulasexternal_id · email · name · job_title · department · center · role · status · language · created_at · updated_atexternal_id,department,statusComma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.
statustexto · vários separados por vírgulasactive · invited · disabledactiveOnly people in these states. Several may be given.
roletexto · vários separados por vírgulasowner · admin · gestor_personas · responsable_local · informatico · editor · revisor · analista · employeeOnly people with these roles. Several may be given.
departmenttexto · vários separados por vírgulasMaintenance,QualityDepartment name, or several separated by commas. Case-insensitive: your directory writing MANTENIMIENTO is not an error.
centertexto · vários separados por vírgulasSite name, or several separated by commas. Case-insensitive.
no_departmentbooleanoOnly people with no department. These are the ones who never see a targeted announcement, so this is the filter that finds a broken provisioning mapping.
languagetexto · vários separados por vírgulases · ca · en · pt · fr · it · de · sv · nl · pl · ro · csOnly people who picked this reading language.
emailtextoExact email address, case-insensitive.
external_idtextoExact external_id, as sent by your HR system over SCIM.
qtextogarciaFree text over name, email and job title. Case-insensitive, matches anywhere in the value.
updated_sincedata ISO 86012026-08-01T00:00:00ZOnly people changed at or after this instant. This is the one that lets you sync every fifteen minutes without pulling two thousand rows every time.
created_sincedata ISO 8601Only people added at or after this instant.
created_untildata ISO 8601Only people added before this instant.

GET /people/{id}

people:read

Uma pessoa concreta, procurada pelo nosso identificador ou pelo vosso. Serve para atualizar uma ficha sem percorrer a lista toda.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoa3f1…Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
fieldstexto · vários separados por vírgulasexternal_id · email · name · job_title · department · center · role · status · language · created_at · updated_atexternal_id,department,statusComma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.

POST /people

people:write

Cria alguém. É para quando o vosso sistema de pessoal não fala SCIM; se falar, melhor SCIM, que também trata das saídas. O departamento e o centro de trabalho indicam-se pelo nome e criam-se se não existirem.

No corpo

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
nameObrigatóriotextocomprimento máx. 120Ana GarcíaFull name, as it should appear on the wall.
emailtextocomprimento máx. 200Work email. Optional: most of the workforce this product is for has none, and they get in with an invite code instead. Unique within the company.
external_idtextocomprimento máx. 200EMP-0042The id this person has in your system. Store it and you can cross both lists later without matching names.
job_titletextocomprimento máx. 120Job title.
departmenttextocomprimento máx. 80Department name. Created if it does not exist. Without one, this person only ever sees company-wide announcements.
centertextocomprimento máx. 80Site name. Created if it does not exist.
roletextoadmin · gestor_personas · responsable_local · informatico · editor · revisor · analista · employeeemployeeWhat they can do. `owner` cannot be given from the API: a key pasted in a cron job must not be able to mint an account that can cancel the subscription.
statustextoactive · invited · disabledactive`active` can sign in; `invited` is created but not counted as signed up yet; `disabled` is created already deactivated, which is what a backfill of former staff needs.
languagetextoes · ca · en · pt · fr · it · de · sv · nl · pl · ro · csReading language. Leave it out and they read in the company's, which is what you want unless you actually know.

PATCH /people/{id}

people:write

Muda só os campos que enviarem. Um que fique de fora não é tocado: uma sincronização que só conhece a função não pode apagar o centro de trabalho a ninguém.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoa3f1…Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.

No corpo

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
nametextocomprimento máx. 120Full name.
emailtextocomprimento máx. 200Work email.
external_idtextocomprimento máx. 200The id this person has in your system.
job_titletextocomprimento máx. 120Job title.
departmenttextocomprimento máx. 80Department name. Created if it does not exist.
centertextocomprimento máx. 80Site name. Created if it does not exist.
roletextoadmin · gestor_personas · responsable_local · informatico · editor · revisor · analista · employeeWhat they can do. `owner` cannot be given from the API.
statustextoactive · invited · disabledTheir state. `disabled` also closes their sessions.
languagetextoes · ca · en · pt · fr · it · de · sv · nl · pl · ro · csReading language.

DELETE /people/{id}

people:write

Desativa; nunca apaga. As sessões fecham-se de imediato, por isso quem sai numa sexta-feira fica fora nessa sexta-feira. O que publicou fica.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoa3f1…Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.

GET /audiences

people:read

Os departamentos e os centros de trabalho que existem, com quantas pessoas há em cada um. É daqui que saem os nomes a que se pode dirigir um comunicado, e o filtro dos grupos vazios é o que encontra um mapeamento de aprovisionamento partido.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
typetextoall · departments · centersallReturn only departments, only sites, or both.
qtextoFree text over the name. Case-insensitive.
with_peoplebooleanoOnly groups that have at least one active person. A department with nobody in it is almost always an old name left behind, and it is the usual cause of an announcement that reaches no one.
sorttextoname · peoplenameField to sort by. Defaults to name.
ordertextoasc · descascSort direction.

GET /announcements

announcements:read

Tudo o que foi publicado, com as leituras e as confirmações de cada peça. Inclui rascunhos e recusados para que um painel possa desenhar o funil inteiro.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
pageinteiromín. 112Page to return. The first page is 1, not 0.
per_pageinteiromín. 1 · máx. 200 · fora do intervalo é cortado em vez de falhar50100How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.
sorttextopublished_at · created_at · updated_at · titlepublished_atField to sort by. Defaults to published_at.
ordertextoasc · descascSort direction.
fieldstexto · vários separados por vírgulastitle · body · status · kind · language · audience · community · section · author · notify · comments_enabled · pinned · acknowledgements · reads · published_at · created_at · updated_at · urlexternal_id,department,statusComma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.
statustexto · vários separados por vírgulasdraft · pending_review · published · rejected · scheduled · archivedpublished,scheduledOnly these states. Several may be given. `scheduled` means it is written and waiting for its hour.
authortextoWho signs it: our id, their email or their external_id. Any of the three.
audience_typetextoall · departments · centersOnly announcements aimed this way.
departmenttexto · vários separados por vírgulasOnly announcements aimed at these departments, by name. Several may be given.
centertexto · vários separados por vírgulasOnly announcements aimed at these sites, by name.
communitytextoOnly announcements in this community, by id or by name.
requires_acknowledgementbooleanoOnly required reading, or only the rest. This is the filter a compliance report is built on.
pinnedbooleanoOnly pinned announcements, or only the rest.
qtextoFree text over title and body. Case-insensitive.
published_sincedata ISO 8601Published at or after this instant.
published_untildata ISO 8601Published before this instant.
created_sincedata ISO 8601Written at or after this instant.
updated_sincedata ISO 8601Changed at or after this instant.

GET /announcements/{id}

announcements:read

Uma publicação concreta. É o passo seguinte ao identificador que uma publicação devolve ou que chega num evento de saída.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe announcement id.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
fieldstexto · vários separados por vírgulastitle · body · status · kind · language · audience · community · section · author · notify · comments_enabled · pinned · acknowledgements · reads · published_at · created_at · updated_at · urlexternal_id,department,statusComma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.

POST /announcements

announcements:write

Publica no quadro de avisos. É preciso dizer quem assina e não há valor por omissão. Se essa pessoa não puder publicar sem revisão, o comunicado entra na fila e a resposta di-lo.

No corpo

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
bodyObrigatóriotextocomprimento máx. 8000The text. Plain text; line breaks are kept.
authorObrigatóriotextoEMP-0042Who signs it: our id, their email or their external_id. Must be an active person in this company.
titletextocomprimento máx. 160Headline.
audienceobjetoWho it goes to. Leave it out and it goes to everyone. Names work as well as ids, case-insensitively, because your ERP has 'Maintenance' and not a uuid of ours. Nothing is created here: an unknown name is a typo in a script, and the reply tells you which one it was.
audience.typetextoall · departments · centersallAim at everyone, at departments or at sites.
audience.idslista["Maintenance","Quality"]Department or site names, or their ids. Required unless type is `all`.
communitytextoWhich community it belongs to, by id or by name. Leave it out and it goes on the main wall, which everybody sees. The author has to belong to the community, or be posting to the default one: an API key is not a way into a community its signer is not in.
sectiontextoWhich section it belongs to, by id or by name. It must belong to the same community.
publish_atdata ISO 86012026-09-01T06:00:00ZPublish later, at this instant. It must be in the future. The reply comes back with `status: scheduled` and the wall shows it when the hour arrives. Leave it out to publish now. Note this means two clocks are involved — yours and ours — so if your system already schedules, prefer calling us at the right moment.
requires_acknowledgementbooleanofalseRequired reading: everyone it reaches gets a notification with a confirm button, and who confirmed is on record. Only signers who can publish without review may ask for it.
notifybooleanotrueWhether it makes a sound. Written the right way round on purpose: the screen has a 'publish without notifying' checkbox because a form that loses a field should still notify, but here you write the whole JSON and a field called `silent` reads wrong at three in the morning. Default is true.
comments_enabledbooleanotrueWhether people can comment on it.
pinnedbooleanofalseKeep it at the top of the wall.
languagetextoes · ca · en · pt · fr · it · de · sv · nl · pl · ro · csWhat language the text is written in. Leave it out and the company's is used. Getting this right matters: a Catalan original labelled as Spanish is shown to a Spanish reader without the 'not translated' notice that tells them nothing is broken.

GET /announcements/{id}/acknowledgements

announcements:read

Quem confirmou uma leitura obrigatória e quem não, com nome e hora. É a única lista nominal de toda a API, e é-o porque esse é justamente o sentido da leitura obrigatória: um ato voluntário, assinado e datado que se pode mostrar a uma inspeção.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe announcement id.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
pageinteiromín. 112Page to return. The first page is 1, not 0.
per_pageinteiromín. 1 · máx. 200 · fora do intervalo é cortado em vez de falhar50100How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.
statetextoall · acknowledged · pendingallEveryone, only those who confirmed, or only those still pending.
departmenttexto · vários separados por vírgulasOnly people in these departments, by name.
centertexto · vários separados por vírgulasOnly people at these sites, by name.
sorttextoname · acknowledged_atnameField to sort by. Defaults to name.
ordertextoasc · descascSort direction.

GET /metrics

metrics:read

O quadro de pessoal de hoje, as entradas, as publicações e o alcance por publicação e por departamento. Os mesmos números do ecrã de análise, calculados pelo mesmo código.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
period_daysinteiromín. 1 · máx. 3653090How many days back the period covers. Applies to every figure with a period, including the reach lists.
includetexto · vários separados por vírgulasworkforce · announcements · reach_by_announcement · reach_by_departmentworkforce,announcementsWhich blocks to compute, comma-separated. Leave it out for all of them. `reach_by_announcement` is the expensive one: skip it if your dashboard only draws the headline figures.

GET /webhooks

webhooks:read

Os vossos endereços registados, cada um com a sua saúde: falhas seguidas, o que o vosso servidor respondeu da última vez e, se o tivermos pausado, porquê.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
pageinteiromín. 112Page to return. The first page is 1, not 0.
per_pageinteiromín. 1 · máx. 200 · fora do intervalo é cortado em vez de falhar50100How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.
eventtextoannouncement.published · announcement.pending_review · announcement.acknowledged · person.created · person.deactivatedOnly endpoints subscribed to this event.
activebooleanoOnly live endpoints, or only the switched-off and paused.

POST /webhooks

webhooks:write

Regista um endereço e os eventos que quer. A resposta traz o segredo de assinatura, e é a única vez em que se vê.

No corpo

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
urlObrigatóriotextocomprimento máx. 500https://hooks.example.com/blastackWhere we POST. https only, no credentials, no fragment.
eventsObrigatóriolistaannouncement.published · announcement.pending_review · announcement.acknowledged · person.created · person.deactivated["announcement.published"]Which events this endpoint wants. At least one.
nametextocomprimento máx. 120Zapier · switchboardWhat it is for, in your words. It is the only thing that lets you delete the right one six months from now.
secrettextocomprimento máx. 200Your own signing secret, if you would rather set it than have one generated. At least 32 characters.
activebooleanotrueStart delivering straight away.

GET /webhooks/{id}

webhooks:read

Um endereço concreto, com a sua saúde.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe webhook endpoint id.

PATCH /webhooks/{id}

webhooks:write

Muda o que enviarem. Pôr o endereço outra vez ativo levanta também a pausa que tenhamos aplicado depois de falhas repetidas e põe o contador a zero.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe webhook endpoint id.

No corpo

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
urltextocomprimento máx. 500Where we POST. https only.
eventslistaannouncement.published · announcement.pending_review · announcement.acknowledged · person.created · person.deactivatedWhich events this endpoint wants. At least one.
nametextocomprimento máx. 120What it is for.
activebooleanoSwitch delivery on or off. Turning it on clears a pause.
rotate_secretbooleanoGenerate a new signing secret and return it once in this reply.

DELETE /webhooks/{id}

webhooks:write

Apaga-o com o seu histórico. Para deixar de receber durante um tempo sem perder o histórico, é melhor desativá-lo.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe webhook endpoint id.

GET /webhooks/{id}/deliveries

webhooks:read

Tudo o que tentámos mandar-vos: tentativas, o que responderam e quando é a próxima. O identificador é o mesmo que viajou no cabeçalho, por isso cruza-se com os vossos registos.

No caminho

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
idObrigatóriotextoThe webhook endpoint id.

Na consulta

ParâmetroTipoValores admitidosPor omissãoExemploO que faz
pageinteiromín. 112Page to return. The first page is 1, not 0.
per_pageinteiromín. 1 · máx. 200 · fora do intervalo é cortado em vez de falhar50100How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.
statustextopending · delivered · given_upOnly deliveries in this state.
eventtextoannouncement.published · announcement.pending_review · announcement.acknowledged · person.created · person.deactivatedOnly deliveries of this event type.
sincedata ISO 8601Only events that happened at or after this instant.

Exemplos

Três coisas que uma fábrica automatiza no primeiro mês

Com o pedido inteiro e a resposta inteira. Mudem a chave e os nomes; o resto funciona tal como está.

Quando a manutenção fecha a ordem de paragem

O ERP regista uma paragem de linha e o quadro de avisos fica a saber sozinho

É o caso que pedem sempre. O vosso sistema já sabe que a linha 3 para na quinta-feira; com esta chamada publica-o ele, dirigido aos departamentos certos e a pedir confirmação de leitura, sem ninguém escrever nada duas vezes.

Pedido

curl -X POST https://app.blastack.com/api/v1/announcements \
  -H "Authorization: Bearer blsk_a1b2c3d4e5f6_YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
       "author": "EMP-0042",
       "title": "Paragem da linha 3 na quinta-feira",
       "body": "Na quinta-feira dia 4, às 06:00, a linha 3 para para manutenção programada. O turno da manhã entra à hora do costume e passa para a linha 2.",
       "audience": {
         "type": "departments",
         "ids": [
           "Manutenção",
           "Qualidade"
         ]
       },
       "requires_acknowledgement": true,
       "notify": true,
       "language": "pt"
     }'

Resposta · 201

{
  "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
  "title": "Paragem da linha 3 na quinta-feira",
  "body": "Na quinta-feira dia 4, às 06:00, a linha 3 para para manutenção programada. O turno da manhã entra à hora do costume e passa para a linha 2.",
  "status": "published",
  "kind": "news",
  "language": "pt",
  "audience": {
    "type": "departments",
    "ids": [
      "9c1d7e5f-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
      "44ab7e5f-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
    ],
    "names": [
      "Manutenção",
      "Qualidade"
    ]
  },
  "community": null,
  "section": null,
  "author": {
    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
    "name": "Ana García",
    "email": "ana.garcia@empresa.com",
    "external_id": "EMP-0042"
  },
  "notify": true,
  "comments_enabled": true,
  "pinned": false,
  "acknowledgements": {
    "required": true,
    "confirmed": 0
  },
  "reads": 0,
  "published_at": "2026-09-01T06:00:00.000Z",
  "created_at": "2026-09-01T06:00:00.000Z",
  "updated_at": "2026-09-01T06:00:00.000Z",
  "url": "https://app.blastack.com/inicio#post-6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40"
}

O destino vai por nome e não por identificador, porque é o que o vosso sistema tem à mão. Se o nome não existir, a resposta diz qual era e não publica nada: meia publicação seria pior.

Quando o sistema de pessoal marca a saída

Alguém sai do sistema de pessoal e fica fora no mesmo dia

Se o vosso sistema falar SCIM, isto já acontece sozinho e esta chamada não é precisa. Se não falar — o normal numa fábrica —, com uma linha a pessoa fica desativada e com as sessões fechadas de imediato. O caminho aceita o vosso próprio identificador, por isso não é preciso manter uma tabela de equivalências.

Pedido

curl -X DELETE https://app.blastack.com/api/v1/people/EMP-0042 \
  -H "Authorization: Bearer blsk_a1b2c3d4e5f6_YOUR_SECRET"

Resposta · 200

{
  "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
  "external_id": "EMP-0042",
  "email": "ana.garcia@empresa.com",
  "name": "Ana García",
  "job_title": "Chefe de turno",
  "department": "Manutenção",
  "center": "Nave 2",
  "role": "editor",
  "status": "disabled",
  "language": null,
  "created_at": "2026-02-11T08:14:00.000Z",
  "updated_at": "2026-09-01T07:40:22.000Z"
}

Desativa; não apaga. O que publicou fica, porque um quadro cheio de buracos não ajuda ninguém. Chamar duas vezes não é erro.

Todas as manhãs, ou na véspera de uma auditoria

Quem confirmou o protocolo, para uma folha de cálculo

Um cenário do Make ou um fluxo do n8n que peça os pendentes e os escreva numa folha: nome, departamento e se confirmaram. É o relatório que uma auditoria pede, e o que evita andar atrás de alguém pelo corredor.

Pedido

curl -G https://app.blastack.com/api/v1/announcements/6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40/acknowledgements \
  --data-urlencode "state=pending" \
  --data-urlencode "department=Manutenção" \
  --data-urlencode "per_page=200" \
  -H "Authorization: Bearer blsk_a1b2c3d4e5f6_YOUR_SECRET"

Resposta · 200

{
  "announcement": {
    "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
    "title": "Paragem da linha 3 na quinta-feira"
  },
  "summary": {
    "recipients": 34,
    "acknowledged": 27,
    "pending": 7
  },
  "data": [
    {
      "person": {
        "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
        "name": "Ana García",
        "email": "ana.garcia@empresa.com",
        "external_id": "EMP-0042",
        "department": "Manutenção",
        "center": "Nave 2"
      },
      "acknowledged": false,
      "acknowledged_at": null
    }
  ],
  "page": 1,
  "per_page": 200,
  "total": 7
}

O resumo conta-se sobre a população inteira e não sobre a página nem sobre o filtro, para que o «12 de 30» continue a querer dizer alguma coisa. A hora da confirmação é a da primeira vez e nunca mais se toca.

Eventos de saída

Ligamos nós, em menos de um minuto

É o que torna isto verdadeiramente ligável. Sem eventos de saída, o disparador do vosso cenário tem de ser «de cinco em cinco minutos, pergunta», e isso é lento, paga-se à passagem e quase nunca encontra nada.

EventoQuando dispara
announcement.publishedSaiu alguma coisa no quadro de avisos, venha por onde vier: o compositor, o editor completo, esta API, a fila de revisão ou uma publicação agendada que chegou à sua hora. O tipo viaja lá dentro, caso só vos interessem os comunicados simples e não os inquéritos ou os eventos.
announcement.pending_reviewUma publicação espera aprovação. Só dispara em empresas que têm a fila de revisão ligada.
announcement.acknowledgedAlguém confirmou uma leitura obrigatória. Um evento por pessoa, com quem e quando.
person.createdAlguém foi criado, tanto faz por onde: aprovisionamento, código de adesão, início de sessão único, ecrã de administração ou esta API.
person.deactivatedAlguém foi desativado. Como aqui não se apaga ninguém, este é o sinal de saída.

O sobrescrito, igual nos cinco

{
  "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "type": "announcement.published",
  "occurred_at": "2026-09-01T06:00:00.000Z",
  "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
  "data": {
    "announcement": {
      "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
      "title": "Parada de la línea 3 el jueves",
      "body": "El jueves 4 a las 06:00 para la línea 3 por mantenimiento programado.",
      "kind": "news",
      "language": "es",
      "requires_acknowledgement": true,
      "audience": {
        "type": "departments",
        "names": [
          "Mantenimiento",
          "Calidad"
        ]
      },
      "community": "Planta de Getafe",
      "author": {
        "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
        "name": "Ana García",
        "email": "ana.garcia@empresa.com",
        "external_id": "EMP-0042"
      },
      "url": "https://app.blastack.com/inicio#post-6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
      "status": "published"
    }
  }
}

O que vai lá dentro muda com o evento; o sobrescrito não. O identificador não muda entre tentativas, por isso descartar um repetido é uma condição de uma linha do vosso lado.

Os cabeçalhos de cada envio

CabeçalhoPara que serve
blastack-eventO que aconteceu. É o valor por onde se ramifica.
blastack-event-idO identificador do evento. Não muda entre tentativas: é aquele em que assentar para nunca executar duas vezes a mesma coisa.
blastack-delivery-attemptQue tentativa é esta, a começar na primeira.
blastack-signatureA assinatura. É a única coisa que separa um envio nosso de um inventado por quem descobrir o vosso endereço.

Verificar a assinatura

A assinatura é HMAC-SHA256 sobre a marca temporal, um ponto e o corpo em bruto, sem interpretar. É o mesmo formato que usam as plataformas de pagamento, ou seja, aquele que provavelmente já escreveram uma vez.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody, headers, secret) {
  const sent = headers["blastack-signature"] ?? "";
  const t = /t=(\d+)/.exec(sent)?.[1];
  const v1 = /v1=([0-9a-f]+)/.exec(sent)?.[1];
  if (!t || !v1) return false;

  const mine = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`, "utf8")
    .digest("hex");

  if (v1.length !== mine.length) return false;
  if (!timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(mine, "hex"))) {
    return false;
  }
  return Math.abs(Date.now() / 1000 - Number(t)) < 300;
}

Comparem em tempo constante e recusem tudo o que tenha uma marca com mais de alguns minutos. Sem essa segunda verificação, uma cópia de um envio antigo pode ser-vos reenviada durante anos e a assinatura continua válida.

Novas tentativas, pausa e porque é que não chega nada

  • Serve qualquer resposta 2xx. Não lemos o corpo: respondam depressa e façam o trabalho depois, porque ao fim de dez segundos desistimos e isso conta como falha.
  • Seis tentativas com espera crescente — um minuto, cinco, quinze, uma hora, três e seis — repartidas por cerca de dez horas e meia. Cobre a instalação de madrugada e a manutenção de fim de semana.
  • Trinta falhas seguidas põem o endereço em pausa, e o motivo fica escrito. Levanta-se voltando a pô-lo ativo, o que também põe o contador a zero.
  • Só ligamos a endereços https públicos. Verifica-se ao registar e outra vez antes de cada envio, porque um nome que hoje resolve para a internet pública pode amanhã resolver para a vossa rede interna.
  • Um endereço registado hoje não recebe o de ontem: uma subscrição conta desde que é criada, para que montar o primeiro cenário não dispare quarenta execuções de uma vez.

Erros

Sempre a mesma forma

Todo o erro devolve o mesmo objeto, com um código pensado para lhe escreverem uma condição por cima. Os códigos nunca mudam de nome: acrescentam-se. A mensagem ao lado é para quem lê o registo, e essa pode mudar.

{
  "error": {
    "status": 403,
    "code": "insufficient_scope",
    "message": "This API key does not have the \"metrics:read\" scope.",
    "required_scope": "metrics:read",
    "granted_scopes": [
      "people:read"
    ]
  },
  "documentation": "https://blastack.com/en/developers"
}

Um parâmetro desconhecido é recusado, não ignorado. Um filtro descartado em silêncio devolve-vos mais dados do que pediram e nada vos avisa.

RateLimit-Limit · RateLimit-Remaining · RateLimit-Reset · Retry-After

CódigoQuando sai
invalid_token401 · A chave falta, está mal escrita ou foi revogada. Não dizemos qual das três.
insufficient_scope403 · A chave é válida mas não tem o âmbito que essa chamada pede. A resposta diz qual era preciso e quais tem.
module_required403 · A empresa não tem contratado o módulo de identidade corporativa, que é aquele a que a API pertence.
read_only403 · O período experimental acabou e a empresa está só de leitura. Só afeta o que escreve.
rate_limited429 · Mais pedidos do que o permitido num minuto. A resposta diz quantos segundos esperar.
invalid_request400 · Falta um campo, sobra ou não se entende. A mensagem nomeia o parâmetro exato e lista os valores que admite.
not_found404 · Não há nada vosso com esse identificador. É também o que se recebe por algo de outra empresa: distinguir os dois casos seria uma forma de descobrir o que existe lá fora.
conflict409 · Choca com algo que já existe: duas pessoas com o mesmo correio, dois endereços de evento iguais. A resposta traz o identificador do que já estava, para se poder continuar.
server_error500 · Partiu-se algo do nosso lado. Nada do erro sai na resposta; inteiro fica no nosso registo.

Limites

O que convém saber antes de escrever o ciclo

  • Cento e vinte pedidos por minuto e por chave. Cada resposta traz quantos faltam e em quantos segundos a contagem recomeça; um 429 traz ainda quanto esperar.
  • As listas vêm de cinquenta em cinquenta e admitem até duzentos por página. A primeira página é a 1, não a 0. Pedir mais de duzentos é cortado em vez de recusado.
  • O período dos indicadores vai até trezentos e sessenta e cinco dias e pede-se por parâmetro. O alcance por publicação é o bloco caro: pode ficar de fora se o vosso painel só desenha os números de cabeçalho.
  • Vinte endereços de evento de saída por empresa. Não é um limite comercial: cada evento entra na fila uma vez por endereço, e quem precisa de mais está a distribuir no sítio errado.
  • Os eventos de saída demoram até um minuto: detetam-se lendo a base de dados e não a partir do botão de publicar, para que o servidor lento de uma integração não possa travar o quadro de ninguém.
  • Só se anuncia o das últimas vinte e quatro horas. Se a nossa tarefa do relógio estivesse parada um dia inteiro, não vos largamos o trimestre de uma vez: as vossas automatizações correriam com dados de anteontem como se fossem de agora.

O que não faz

Dito aqui para não o descobrirem a meio

Quem vai integrar precisa dos limites antes de começar, não quando já tem meio cenário montado. Esta lista é o que um programador vai procurar e não vai encontrar, e atualiza-se quando alguma coisa deixa de ser verdade.

  • Não há ecrã para criar chaves. Hoje emitem-se por consola com npm run clave:api, e emitimo-las nós. Um ecrã honesto precisaria dos seus textos nos doze idiomas da aplicação, e meio ecrã traduzido não se publica.
  • Os eventos de saída precisam de uma linha de cron no servidor. Sem ela nada se parte, e esse é que é o problema: tudo o resto continua a funcionar e simplesmente não sai nada. Vê-se no registo de entregas, que estaria vazio.
  • O segredo de assinatura mostra-se uma só vez: ao criar o endereço ou ao rodá-lo. Não se pode voltar a ler, só rodar. Guardem-no nesse momento.
  • Rodar o segredo não tem período de graça. O envio seguinte já vai assinado com o novo: mudem-no primeiro no vosso servidor e peçam a rotação depois.
  • Vinte endereços de evento de saída por empresa. Se precisarem de mais, a distribuição tem de ser feita na vossa ferramenta a partir de uma só entrega.
  • Não se chega ao chat, nem aos inquéritos, nem aos eventos, nem aos reconhecimentos, nem aos álbuns. Aparecem na lista de publicações e nos eventos de saída com o seu tipo, mas não se criam nem se leem por si.
  • Não se chega aos documentos nem se carregam ficheiros. Nem imagens dentro de uma publicação: o corpo é texto simples.
  • Uma publicação já feita não se edita nem se apaga, e os comentários não se escrevem nem se leem.
  • Não se procuram pessoas por pesquisa parcial no correio, e ninguém é apagado a sério: apagar é um pedido por escrito que tratamos nós, e anonimiza em vez de eliminar.
  • Os indicadores são agregados e não há forma de tirar quem leu o quê. A única lista com nomes é a das leituras obrigatórias, que existe para se poder mostrar a uma inspeção.
  • Não há ambiente de testes separado, nem bibliotecas oficiais para nenhuma linguagem, nem paginação por cursor. É JSON sobre HTTPS e chama-se com o que tiverem.

Para máquinas

Se quem está a ler isto é um assistente

Metade de uma avaliação de compra é hoje alguém a perguntar a um assistente se isto se liga a alguma coisa. Por isso a resposta está escrita para se poder ler sem conta e sem adivinhar nada.

  • A especificação OpenAPI 3.1 está num endereço público e estável, sem chave, com origem cruzada permitida e sem tocar na base de dados uma única vez. Descreve os dezassete pontos de acesso com os seus parâmetros, os valores admitidos, a forma de cada resposta e os cinco eventos de saída.
  • Não está escrita à mão: gera-se do mesmo catálogo com que cada ponto de acesso verifica o que lhe chega, e as formas das respostas saem de executar o próprio código que atende os pedidos. Não pode dizer algo diferente do que faz.
  • Esta página tem a mesma estrutura nos seis idiomas, com títulos que dizem o que está por baixo e com tabelas a sério, não imagens nem colunas fingidas.
  • Há ainda um llms.txt com a versão curta, com a secção de integração escrita à parte e com os endereços e os âmbitos em texto simples.

Se vos faltar alguma coisa dessa lista, digam

O que mais for pedido é o próximo a escrever-se. E se algo desta página não bater certo com o que o servidor responde, é uma falha nossa e queremos sabê-lo no mesmo dia.