Escopo
As rotas de admin de In-App ficam em /v1/apps/{app_id}/in-app-messages e aceitam uma sessão de admin ou uma chave REST (Authorization: Bearer rk_...), de modo que um backend ou um script pode gerenciar mensagens sem o painel. Toda rota tem o escopo do App no caminho. Os formatos de requisição e resposta campo a campo estão na Referência da API de In App gerada.
Dispositivos não usam essas rotas. As rotas voltadas ao SDK (eligible e events) usam uma chave de cliente e estão descritas no Contrato de mensagens in-app.
Criar e ativar
POST /v1/apps/{app_id}/in-app-messages cria um rascunho por padrão, ou uma mensagem ativa quando o corpo tem "status": "active":
curl -X POST https://your-instance/v1/apps/{app_id}/in-app-messages \
-H "Authorization: Bearer $REST_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Example announcement",
"format": "top",
"title": "Example title",
"body": "Example body.",
"buttons": [
{ "label": "Open", "action": { "type": "deeplink", "value": "exampleapp://offers/42" } }
],
"platforms": ["ios", "android"],
"frequency": { "type": "once" }
}'- Campos desconhecidos são rejeitados.
formaté o layout:top,center(padrão no painel),bottomoufull.frequencyéonce,n_times(commax) ouinterval(cominterval_seconds). - A imagem é
media_id(um upload da Media API) ou umaimage_urlviahttps, nunca ambos. - Os ids dos botões são atribuídos pelo servidor e permanecem estáveis entre edições.
- Links aceitam
httpse esquemas customizados de app;http,javascript,data,file,vbscript,blobeaboutsão rejeitados. - Uma ação de botão é
url,deeplinkoupush_permission.push_permissionpede ao dispositivo a permissão de notificação, não recebevalue(um valor resulta em422 invalid_content, combuttons[0].action.value: invalid_action_valueemdetails.fieldspara o primeiro botão) e não é aceita emimage_action. O servidor oculta uma mensagem com essa ação dos dispositivos que já recebem push e dos dispositivos em um SDK que ainda não a suporta; veja o contrato. - Um corpo rejeitado resulta em
422 invalid_contentcomdetails.fieldsmapeando cada campo a um código.
PATCH .../{id} atualiza o conteúdo (uma chave omitida mantém seu valor, null limpa media_id, image_url, starts_at, ends_at, style, image_action e dismiss_after_seconds; buttons, platforms, segment_ids, exclude_segment_ids, frequency, style, image_action e trigger são substituídos por inteiro, e null ou [] esvazia as duas listas de segmentos). Mudanças de status passam por POST .../{id}/activate e POST .../{id}/pause. Edições em uma mensagem ativa chegam aos dispositivos no próximo fetch. DELETE .../{id} faz soft-delete da mensagem: ela sai das listas e dos dispositivos, suas métricas continuam legíveis por id, e os segmentos que ela usava podem ser excluídos novamente.
Aparência, comportamento e trigger
Quatro campos opcionais definem como o SDK exibe a mensagem. Eles fazem parte do mesmo corpo de criação e de PATCH:
{
"format": "center",
"style": {
"background_color": "#102030",
"text_color": "#FFFFFF",
"button_color": "#E94B8B",
"button_text_color": "#FFFFFF",
"text_align": "center",
"margin": true
},
"image_url": "https://cdn.example.com/offer.png",
"image_action": { "type": "url", "value": "https://example.com/offer" },
"dismiss_after_seconds": 8,
"trigger": { "type": "event", "name": "checkout_done" }
}stylecontém até cinco tokens (background_color,text_color,button_color,button_text_colorcomo#RRGGBB;text_aligncomoleft,centerouright) emargin. O conjunto é fechado: uma chave desconhecida resulta em422 invalid_contentcomstyle.<key>: unknown. Uma cor que não seja#RRGGBBresulta emstyle.<key>: invalid_color, um alinhamento fora dos três valores resulta emstyle.text_align: invalid_align. As cores são armazenadas em maiúsculas.styleénullquando não definido, e umPATCHque envia um objeto substitui o style inteiro, então um token omitido é limpo.image_actiontorna a imagem tocável. Ela tem o formato e as regras de link de uma ação de botão e exige uma imagem (image_action: requires_imagecaso contrário). Limpe a ação junto com a imagem.dismiss_after_secondsfecha a mensagem após 1 a 300 segundos em tela (out_of_rangecaso contrário).nulla mantém até o usuário fechá-la.triggeré{"type": "session_start"}(o padrão, e o valor que a API retorna quando nenhum é definido) ou{"type": "event", "name": "..."}com um nome de 1 a 64 caracteres entrea-z,0-9,_,.e-. Códigos:trigger: invalid_type,trigger.name: required,invalid_name,too_long, enot_allowedpara um nome em um triggersession_start."trigger": nullem umPATCHdefine session start.
GET /v1/apps/{app_id}/in-app-messages/trigger-events retorna { "events": ["checkout_done", "..."] }: os nomes de evento distintos usados pelas mensagens não excluídas do App, ordenados, no máximo 50. Os nomes de evento não são registrados em nenhum outro lugar; a lista só alimenta sugestões.
Como o SDK usa esses campos está no Contrato de mensagens in-app.
Segmentação com segmentos
Uma mensagem segmenta os mesmos segmentos que você usa para push. segment_ids lista os segmentos para os quais ela é exibida e exclude_segment_ids lista aqueles cujos dispositivos nunca a veem:
- Um dispositivo vê a mensagem quando corresponde a pelo menos um segmento em
segment_ids, ou quando essa lista está vazia (todos os dispositivos), e não corresponde a nenhum dos segmentos emexclude_segment_ids. - Cada lista comporta até 10 segmentos. Um segmento não pode se repetir nem aparecer nas duas listas. Os campos resultam em
422 invalid_contentcom os códigosinvalid_id,duplicate_id,included_and_excludedoutoo_many. - Um segmento de outro App, ou que não existe, resulta em
404 segment_not_found. - Um segmento cujas regras armazenadas não são mais válidas nunca corresponde. Uma mensagem que o inclui não é exibida, e uma mensagem que o exclui também não é exibida, de modo que uma exclusão ilegível nunca amplia a audiência.
- Um segmento usado por uma mensagem não excluída não pode ser excluído (
409 segment_in_use).
Limite do plano
O plano limita quantas mensagens podem estar ativas ao mesmo tempo por App. Ativar além do limite, por activate ou criando uma mensagem ativa, resulta em:
{ "error": { "code": "in_app_limit_exceeded", "message": "...", "details": { "reason": "in_app_limit_exceeded" } } }com status 402. Uma assinatura inativa resulta em 402 subscription_inactive. Rascunhos e mensagens pausadas não ocupam uma vaga, e uma mensagem após seu ends_at deixa de contar.
Mudar para um plano que permite menos mensagens ativas do que um App tem no momento não é rejeitado. Quando a mudança entra em vigor, o Notti pausa as mensagens acima do novo limite, mantendo ativas as mais antigas (por data de criação). A pausa é aplicada por uma verificação em segundo plano que roda a cada cerca de 5 minutos, então pode haver uma pequena defasagem em relação à mudança de plano. Mensagens pausadas mantêm conteúdo e métricas, e você pode reativá-las enquanto o App estiver abaixo do limite.
Listar e filtrar
GET /v1/apps/{app_id}/in-app-messages retorna as mensagens da mais nova para a mais antiga, com paginação por cursor (limit de 1 a 100, cursor vindo de next_cursor).
statusfiltra pelo status de exibição:all(padrão),draft,active,scheduled,pausedoucompleted.scheduledé uma mensagem ativa cujostarts_atainda está no futuro, ecompletedé uma mensagem ativa cujoends_atjá passou. O campostatusarmazenado é apenasdraft,activeoupaused; cada item também trazdisplay_status.searchbusca no nome da mensagem (até 200 caracteres).- Cada item tem
impressions,reachectrde todo o período, e a resposta temactive_counteactive_limit(nullquando não há limite).
Estimativa de audiência
POST /v1/apps/{app_id}/in-app-messages/audience-estimate com { "segment_ids": ["..."], "exclude_segment_ids": ["..."], "platforms": ["ios"] } retorna { "reach": N }: quantos dispositivos do App a audiência corresponde agora, com as mesmas regras de inclusão e exclusão acima. Deixe segment_ids vazio para todos os dispositivos e platforms vazio para todas as plataformas. Os dispositivos contam permitindo push ou não.
Métricas
GET /v1/apps/{app_id}/in-app-messages/{id}/metrics?range=14d retorna totais, uma série diária preenchida com zeros, cliques por botão (uma linha com button_id: "image" vem primeiro quando a mensagem tem uma ação de imagem) e detalhamentos. totals traz dismissers, os dispositivos que fecharam a mensagem por conta própria, e auto_dismissed, os dispositivos que apenas a viram fechar pelo timer. range é 7d, 14d (padrão), 30d, 90d, ou custom com from e to (YYYY-MM-DD, to inclusivo). Mensagens excluídas, pausadas e encerradas continuam legíveis. As definições das métricas estão em O que as métricas significam.