Bem-vindo à API do Reportei Connect
O Reportei Connect é um conector de dados: ele coloca dentro do seu software as mais de 50 integrações que o Reportei já mantém com redes sociais, plataformas de anúncios e ferramentas de marketing. Em vez de implementar e manter um conector para cada plataforma, você acessa todas por uma única API, com autenticação, coleta e renovação de tokens resolvidas pelo Connect.
O modelo é direto: o seu merchant cria customers, cada customer conecta integrações através de uma sessão, e as métricas são consultadas a partir de cada integração conectada.
Para gerar o seu token de merchant, é preciso ter uma conta no Reportei Connect com o trial iniciado.
Iniciar trial no Reportei ConnectBase URL
https://connect.reportei.com/apiAutenticação
A API usa dois tokens: o Bearer Token do merchant, presente em todas as chamadas, e o customer token, exigido nos endpoints que acessam dados de um cliente específico.
Bearer Token (Merchant)
Para autenticar requisições relacionadas à sua conta de merchant, inclua seu token de acesso no cabeçalho Authorization.
Authorization: Bearer YOUR_ACCESS_TOKENCustomer Token
Para acessar dados de um cliente específico, use o token gerado para ele. Este token deve ser enviado no cabeçalho x-customer-token, juntamente com o Bearer Token.
x-customer-token: CUSTOMER_API_TOKENImportante
O customer token é gerado apenas uma vez na criação do cliente. Certifique-se de armazená-lo com segurança, pois ele não pode ser recuperado posteriormente.
Sem x-customer-token, a resposta é 401
Uma rota que acessa dados de um cliente exige o x-customer-token. Sem ele, ou com um token inválido, ela responde 401 e nunca devolve os dados de todos os seus clientes. A visão de todos os clientes fica nas rotas de panorama em /merchants/*, que pedem só o Bearer.
Paginação, Respostas e Erros
Convenções que valem para todos os endpoints da API: como paginar listas, qual é a estrutura das respostas e como interpretar os códigos de status.
Paginação
Endpoints que retornam listas de recursos suportam paginação através dos parâmetros page e per_page.
| Nome | Descrição |
|---|---|
| page | Número da página (padrão: 1) |
| per_page | Itens por página (padrão: 15, máximo: 100) |
Requisição
curl -X GET "https://connect.reportei.com/api/customers?page=2&per_page=20" \
-H "Authorization: Bearer YOUR_API_TOKEN"Resposta
{
"data": [...],
"meta": {
"current_page": 2,
"per_page": 20,
"total": 150,
"total_pages": 8
}
}Formato de Respostas
Todas as respostas da API seguem um formato JSON consistente para facilitar o processamento dos dados.
Resposta de sucesso
{
"data": {
"id": "uuid",
"type": "resource_type",
"attributes": {
// Resource attributes
}
}
}Resposta de lista
{
"data": [
{
"id": "uuid",
"type": "resource_type",
"attributes": { ... }
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 100
}
}Resposta de erro
{
"error": {
"code": "error_code",
"message": "Human readable error message",
"details": {
// Additional error information
}
}
}Tratamento de Erros
A API utiliza códigos de status HTTP padrão para indicar o sucesso ou falha de uma requisição.
200 OK
Requisição bem-sucedida
201 Created
Recurso criado com sucesso
400 Bad Request
Requisição inválida ou malformada
401 Unauthorized
Token de autenticação ausente ou inválido
403 Forbidden
Sem permissão para acessar o recurso
404 Not Found
Recurso não encontrado
422 Unprocessable Entity
Validação falhou nos dados enviados
429 Too Many Requests
Limite de taxa excedido
500 Internal Server Error
Erro no servidor
Exemplo de erro
{
"error": {
"code": "validation_error",
"message": "The given data was invalid",
"details": {
"email": ["The email field is required"],
"name": ["The name must be at least 3 characters"]
}
}
}Merchants
Consulte as configurações do seu merchant, os totais e o panorama da sua conta, as integrações liberadas no seu plano e o catálogo completo de plataformas do Connect.
Panorama da conta
As rotas em /merchants/* leem o que o Connect guarda de todos os seus clientes, só com o Bearer e sem x-customer-token, para você acompanhar o que foi feito pela API: os totais da conta, as campanhas, os conjuntos, os anúncios, os posts, as mídias e as execuções de tools. Nessas rotas, customer, customer_integration e customer_toolkit são filtros opcionais, nunca credencial: um uuid que não é seu responde 403.
/merchants/settingsSettings
Retorna informações sobre o seu merchant, incluindo limites de taxa (rate limiting) e a lista de integrações disponíveis para conexão.
{
"merchant": {
"uuid": "c1e6c85a-6441-4107-ab36-b8bf581b40e0",
"name": "Reportei",
"app_url": "https://reportei.com",
"redirect_url": "https://reportei.com",
"created_at": "2024-09-24 17:07:26",
"updated_at": "2024-09-24 17:07:26",
"rate_limit_per_minute": "999",
"rate_limit_per_second": "999",
"total_customers": 2,
"available_integrations": [
{
"name": "Instagram Business",
"slug": "instagram_business"
},
{
"name": "Facebook",
"slug": "facebook"
}
]
}
}/merchants/statsTotais da conta
Quantos clientes, conexões, posts e anúncios a sua conta tem no Connect, contados na hora do pedido, sem período. posts.total conta os posts pedidos pela API em qualquer status. ads.total conta os anúncios que o Connect criou, apagados incluídos; ads.failed conta as criações que falharam (POST /ads, /ads/boost e /ads/{id}/duplicate), que não existem na conta e por isso ficam fora do total.
{
"data": {
"customers": 1,
"customer_integrations": 3,
"posts": {
"total": 12,
"published": 9,
"failed": 1
},
"ads": {
"total": 7,
"failed": 1
}
}
}/merchants/ads/campaignsCampanhas de todos os clientes
As campanhas que o Connect guarda, de todos os seus clientes, como no GET /ads/campaigns com source=connect. Filtros customer, customer_integration, status, effective_status e include_deleted; paginação por page e per_page. A resposta mostra o último estado conhecido (synced_at); refresh=true relê a conta e exige customer_integration, a conexão pela qual ela é lida.
{
"data": [
{
"id": "120250000000000001",
"uuid": "9a1d3f52-7c1e-4c55-9b0e-5c3f1c6a2e10",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"name": "Black Friday",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": {
"amount": 50,
"type": "daily",
"level": "campaign",
"currency": "BRL"
},
"adsets_count": 1,
"facebook_ads": {
"objective": "traffic",
"spend_cap": null,
"bid_strategy": "lowest_cost"
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/merchants/ads/campaigns?page=1",
"last": "https://connect.reportei.com/api/merchants/ads/campaigns?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/ads/campaigns",
"per_page": 15,
"to": 1,
"total": 1
}
}/merchants/ads/adsetsConjuntos de todos os clientes
Os conjuntos de todos os seus clientes. Mesmos filtros e refresh das campanhas, mais campaign_id (o id da campanha ou o uuid).
{
"data": [
{
"id": "120250000000000002",
"uuid": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"name": "BR 25-45",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": null,
"schedule": {
"start": "2026-11-20T00:00:00-0300",
"end": null
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45,
"genders": [
"all"
]
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"bid_strategy": null,
"bid_amount": null,
"placements": {},
"targeting": {
"interests": [],
"custom_audiences": [],
"excluded_custom_audiences": [],
"advantage_audience": false,
"raw": {}
}
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/merchants/ads/adsets?page=1",
"last": "https://connect.reportei.com/api/merchants/ads/adsets?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/ads/adsets",
"per_page": 15,
"to": 1,
"total": 1
}
}/merchants/adsAnúncios de todos os clientes
Os anúncios de todos os seus clientes. Mesmos filtros e refresh das campanhas, mais campaign_id e adset_id, que aceitam id ou uuid. effective_status=disapproved traz os reprovados de todas as contas, com o motivo em issues.
{
"data": [
{
"id": "120250000000000003",
"uuid": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"adset_id": "120250000000000002",
"name": "Black Friday, imagem",
"status": "active",
"effective_status": "pending_review",
"issues": [],
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"description": null,
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"thumbnail_url": null,
"facebook_ads": {
"creative_id": "120250000000000004",
"page_id": "1068595869664481",
"instagram_account_id": null,
"post_id": null
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/merchants/ads?page=1",
"last": "https://connect.reportei.com/api/merchants/ads?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/ads",
"per_page": 15,
"to": 1,
"total": 1
}
}/merchants/postsPosts de todos os clientes
Os posts pedidos pela API, de todos os seus clientes, do mais recente para o mais antigo, com o customer_uuid de cada um. Filtros customer, customer_integration, status (created, publishing, published ou failed) e external_reference; per_page de 1 a 100, padrão 15.
{
"data": [
{
"uuid": "9f1c2b7e-1d3a-4c55-9a8e-2f6b0c1d4e5f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"external_reference": "post-2026-10-08-001",
"integration_slug": "instagram_business",
"source_id": "17841400000000000",
"status": "published",
"publishing": { "status": "published", "external_id": "18393642439204130", "error": null, "published_at": "2026-10-08 14:00:42" },
"...": "..."
},
{
"uuid": "1b2e7c90-55aa-4f0e-8d0c-3a9e6f7d2c11",
"customer_uuid": "6c1f0a9e-2b3d-4e5f-9a8b-7c6d5e4f3a2b",
"external_reference": "fb-post-2026-10-09-001",
"integration_slug": "facebook",
"source_id": "104512345678901",
"status": "failed",
"publishing": {
"status": "failed",
"external_id": null,
"error": { "code": "324", "message": "Missing or invalid image file." },
"published_at": null
},
"...": "..."
}
],
"links": {
"first": "https://connect.reportei.com/api/merchants/posts?page=1",
"last": "https://connect.reportei.com/api/merchants/posts?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/posts",
"per_page": 15,
"to": 2,
"total": 2
}
}/merchants/tools/executionsExecuções de tools de todos os clientes
As execuções de POST /tools/{tool_slug}/execute de todos os seus clientes, da mais recente para a mais antiga, cada uma com o seu customer_uuid. Filtros customer, customer_toolkit, toolkit_slug e tool_slug; per_page de 1 a 100, padrão 15. Um customer ou customer_toolkit que não é seu, ou uma conexão que não é do customer informado, responde 403.
{
"data": [
{
"uuid": "0d3c8b4a-2e1f-4c6d-9a7b-5e4f3a2b1c0d",
"tool_slug": "jira_get_issue",
"toolkit_slug": "jira",
"customer_toolkit": "7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6e",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"method": "GET",
"url": "https://acme.atlassian.net/rest/api/3/issue/KEY-123",
"payload": {
"issueIdOrKey": "KEY-123",
"fields": "summary,status"
},
"http_status": 200,
"response": {
"id": "10001",
"key": "KEY-123",
"fields": {
"summary": "Fix the login",
"status": {
"name": "In Progress"
}
}
},
"error_message": null,
"duration_ms": 412,
"created_at": "2026-10-19T12:00:00.000000Z"
}
],
"links": {
"first": "https://connect.reportei.com/api/merchants/tools/executions?toolkit_slug=jira&page=1&per_page=15",
"last": "https://connect.reportei.com/api/merchants/tools/executions?toolkit_slug=jira&page=1&per_page=15",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/tools/executions",
"per_page": 15,
"to": 1,
"total": 1
}
}/merchants/mediaMídias de todos os clientes
As mídias que ainda valem, de todos os seus clientes, da mais recente para a mais antiga, cada uma com o seu customer_uuid. Filtros customer e status (uploading ou ready); per_page de 1 a 100, padrão 15. Um customer que não é seu responde 403.
{
"data": [
{
"uuid": "8b1e4f2a-6c3d-4e5f-9a0b-1c2d3e4f5a6b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "ready",
"type": "image",
"mime_type": "image/jpeg",
"name": "verao-4x5.jpg",
"size_bytes": 482133,
"url": "https://file.connect.reportei.com/media/2f0c.../8b1e....jpg",
"expires_at": "2026-11-29T12:00:00.000000Z",
"upload": null,
"created_at": "2026-10-30T12:00:00.000000Z"
}
],
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/merchants/media",
"per_page": 15,
"to": 1,
"total": 1
}
}/integrationsListar Plataformas
Retorna o catálogo completo de plataformas que o Connect coleta, liberadas ou não no seu plano. O slug de cada uma é o que GET /metrics/{integration_slug} e as sessões de integração recebem.
{
"data": [
{
"id": 1,
"name": "Instagram Business",
"slug": "instagram_business",
"type": 1,
"color": "#FC4ADE",
"icon": { "name": "social.instagram_business", "color": "pink-600" },
"enabled": true,
"created_at": "2024-09-13 14:12:56",
"updated_at": "2024-09-13 14:12:56"
},
{
"id": 3,
"name": "Meta Ads",
"slug": "facebook_ads",
"type": 3,
"color": "#0668E1",
"icon": { "name": "social.meta_ads", "color": "indigo-600" },
"enabled": true,
"created_at": "2024-09-13 14:12:56",
"updated_at": "2024-09-13 14:12:56"
}
]
}Use sua aplicação
Vale para as redes da Meta (facebook_ads, facebook, instagram_business e instagram) e para os toolkits que conectam por OAuth. Nas redes da Meta, seus clientes conectam por padrão pelos apps do Reportei; cadastrando o seu próprio app, a tela de consentimento passa a mostrar a sua marca e o uso da API conta na cota do seu app. As demais integrações seguem nos apps do Reportei.
Toolkits que conectam por OAuth
Estas rotas também cadastram o app de um toolkit com modo oauth2: use o slug do toolkit no lugar de integration_slug. O toolkit não tem app do Reportei, então o modo oauth2 dele só conecta com o seu. Os passos, as permissões e os limites abaixo são das redes da Meta; o fluxo do toolkit está na seção Toolkits.
Chaves de LLM
As mesmas rotas cadastram a sua chave de API da OpenAI ou da Anthropic para o gateway de LLM, com openai ou anthropic no lugar do slug e o corpo {"api_key": "..."}. Detalhes na seção LLM.
Como configurar
Registre a callback_url de cada integração no seu app: em "Valid OAuth Redirect URIs" no Facebook Login, ou em "Business login settings" no Login do Instagram. O endereço segue o padrão abaixo e também volta na resposta do cadastro.
Cadastre o App ID e o App Secret com POST /merchants/credentials/{integration_slug}, informando em scopes só as permissões que o seu app tem.
Abra a sessão de integração como sempre. A partir daí, toda conexão nova dessa integração é feita com o seu app.
A callback_url de cada integração
Troque {integration_slug} pelo slug da integração (facebook_ads, facebook, instagram_business ou instagram) ou do toolkit. Se preferir receber o callback no seu domínio, informe redirect_url no cadastro e redirecione de lá para este endereço, com a query intacta.
https://connect.reportei.com/api/oauth/{integration_slug}/callbackImportante
Registre a callback_url no app antes de abrir a primeira sessão: a Meta recusa o login quando o endereço não está na lista do app.
Deixe "Require App Secret" desligado nas configurações avançadas do app.
As quatro integrações e o app de cada uma
Três integrações usam o mesmo app do Facebook. A API do Instagram com Login do Instagram tem um app próprio dentro do mesmo painel, com outro App ID e outro App Secret, então instagram é uma credencial separada.
| integration_slug | App na Meta | Onde registrar a callback_url |
|---|---|---|
| facebook_ads | App do Facebook, com Facebook Login for Business | Facebook Login for Business → Settings → "Valid OAuth Redirect URIs" |
| App do Facebook, com Facebook Login for Business | Facebook Login for Business → Settings → "Valid OAuth Redirect URIs" | |
| instagram_business | App do Facebook, com Facebook Login for Business | Facebook Login for Business → Settings → "Valid OAuth Redirect URIs" |
| Instagram app, com a API do Instagram com Login do Instagram | Instagram → API setup with Instagram login → "Business login settings" |
Permissões
Sem scopes, o Connect pede a lista padrão dos apps do Reportei, que inclui permissões que o seu app pode não ter. Cadastre em scopes só o que o seu app tem. Estas são as listas mínimas para coletar métricas:
| integration_slug | scopes mínimos |
|---|---|
| facebook_ads | email, pages_read_engagement, pages_read_user_content, read_insights, ads_read, ads_management, business_management, pages_show_list |
| email, public_profile, pages_read_engagement, pages_read_user_content, read_insights, business_management | |
| instagram_business | ads_management, ads_read, business_management, email, instagram_basic, instagram_manage_insights, pages_read_engagement, pages_read_user_content, pages_show_list, read_insights |
| instagram_business_basic, instagram_business_manage_insights |
A sua lista substitui a padrão por inteiro. Se os seus clientes publicam pelo Connect, acrescente a permissão de publicação: pages_manage_posts para facebook, instagram_content_publish para instagram_business e instagram_business_content_publish para instagram.
/merchants/credentials/{integration_slug | toolkit_slug}Consultar app
Retorna o app cadastrado para a integração ou para o toolkit, sem o client_secret, com a callback_url que precisa estar no app e quantas conexões ativas ele emitiu.
{
"credential": {
"integration": "facebook_ads",
"client_id": "1234567890123456",
"redirect_url": null,
"scopes": null,
"callback_url": "https://connect.reportei.com/api/oauth/facebook_ads/callback",
"active_connections": 3,
"created_at": "2026-10-13T12:00:00.000000Z",
"updated_at": "2026-10-13T12:00:00.000000Z"
}
}/merchants/credentials/{integration_slug | toolkit_slug}Cadastrar app
Cadastra o seu app para a integração da Meta ou para o toolkit OAuth, com o slug dele no caminho. A partir daí, toda conexão nova dos seus clientes pela sessão dessa integração é feita com ele; num toolkit que só conecta por OAuth, este cadastro vem antes da primeira sessão. Um app por integração ou toolkit: para trocar, use PUT.
Parâmetros
| Campo | Descrição |
|---|---|
| client_id | App ID do seu app na Meta, ou o client ID do app OAuth do toolkit (obrigatório). Para instagram, o do Instagram app. |
| client_secret | App Secret do mesmo app (obrigatório). Guardado criptografado e nunca devolvido pela API. |
| redirect_url | Opcional. Só se você quer receber o callback no seu domínio e redirecionar de lá para a callback_url. Precisa ser https. Em branco, o consentimento redireciona direto para a callback_url. |
| scopes | Opcional. Lista de permissões que substitui a padrão por inteiro no consentimento. Em branco, vale a lista padrão: a dos apps do Reportei nas redes da Meta, ou a do toolkit, em auth[].scopes. |
{
"client_id": "1234567890123456",
"client_secret": "YOUR_APP_SECRET",
"scopes": [
"email",
"pages_read_engagement",
"pages_read_user_content",
"read_insights",
"ads_read",
"ads_management",
"business_management",
"pages_show_list"
]
}/merchants/credentials/{integration_slug | toolkit_slug}Substituir app
Substituição completa: redirect_url e scopes ausentes voltam ao padrão. Trocar o client_id invalida as conexões ativas do app anterior, e o total vem em invalidated_connections. Um client_secret novo do mesmo app não invalida nada.
{
"client_id": "6543210987654321",
"client_secret": "YOUR_NEW_APP_SECRET",
"redirect_url": "https://sua-aplicacao.com/oauth/meta/callback",
"scopes": [
"email",
"pages_read_engagement",
"pages_read_user_content",
"read_insights",
"ads_read",
"ads_management",
"business_management",
"pages_show_list"
]
}/merchants/credentials/{integration_slug | toolkit_slug}Remover app
Nas redes da Meta, as conexões novas voltam aos apps do Reportei; num toolkit, a sessão volta ao formulário de chave, ou é recusada quando ele só conecta por OAuth. As conexões ativas feitas com o seu app são marcadas como expired, e o total vem em invalidated_connections.
{
"message": "Credential removed",
"invalidated_connections": 3
}Trocar ou remover o app invalida as conexões
PUT com outro client_id, e DELETE, marcam como expired toda conexão ativa feita com o app anterior, porque os tokens dele não servem em outro app. A resposta traz o total em invalidated_connections, e cada cliente precisa reconectar pela sessão. Um client_secret novo do mesmo app (o reset do secret no painel da Meta) não invalida nada, e mudar só redirect_url ou scopes também não.
Essa invalidação não dispara o webhook customer_integration.status_updated. Use o total da resposta para avisar os seus clientes.
O que muda com o seu app
As conexões feitas antes do cadastro continuam nos apps do Reportei até o cliente reconectar. O campo active_connections diz quantas conexões ativas estão no seu app.
Nas redes da Meta, o Connect não verifica nem renova os tokens emitidos pelo seu app, e o token de usuário dura cerca de 60 dias. Quando a Meta expira ou invalida um deles, a conexão segue como active e a coleta volta sem dados até o cliente reconectar pela sessão.
No Instagram, stories e o histórico de seguidores (novos seguidores e a evolução no período) dependem de uma coleta diária feita com o app do Reportei, que não inclui as conexões feitas com o seu app. O total atual de seguidores vem normalmente.
Erros desta superfície
Além dos erros comuns da API, estes são próprios do cadastro do app. Falha de validação do corpo volta 422 com errors; as demais recusas voltam message e error_code.
| Status | error_code | Descrição |
|---|---|---|
| 404 | credential_not_found | Nenhum app cadastrado para esta integração. |
| 409 | credential_already_exists | Já existe um app cadastrado para esta integração. Troque com PUT ou remova com DELETE. |
| 422 | credential_unsupported_integration | A integração ou o toolkit não aceita app próprio. Aceitam facebook_ads, facebook, instagram_business, instagram e os toolkits com modo oauth2. |
| 422 | credential_not_available | Esta conta não pode cadastrar um app próprio. |
| 422 | Falha de regra do corpo: campo obrigatório ausente, redirect_url sem https ou um scope com caractere inválido. |
Customers
Gerencie os clientes associados ao seu merchant. Clientes representam os usuários finais que utilizarão as integrações.
/customersListar Clientes
Retorna uma lista paginada de todos os clientes associados ao seu merchant.
{
"data": [
{
"uuid": "caedbbbe-7d93-476f-b965-bf97cd7ce874",
"name": "Reportei Customer 1",
"created_at": "2024-09-24 17:07:34",
"updated_at": "2024-09-24 17:07:34",
"trial_ends_at": "2024-10-08 17:15:12",
"is_paying": true,
"merchant": "Reportei"
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 2,
"total_pages": 1
}
}/customers/{uuid}Obter Cliente
Retorna os detalhes de um cliente específico através do seu UUID.
{
"customer": {
"uuid": "caedbbbe-7d93-476f-b965-bf97cd7ce874",
"name": "Reportei Customer 1",
"created_at": "2024-09-24 17:07:34",
"updated_at": "2024-09-24 17:07:34",
"trial_ends_at": "2024-10-08 17:15:12",
"is_paying": true,
"merchant": "Reportei"
}
}/customersCriar Cliente
Cria um novo cliente associado ao seu merchant. O api_token gerado neste momento é crucial e não poderá ser recuperado depois.
{
"name": "Nome do Novo Cliente"
}/customers/{uuid}Atualizar Cliente
Atualiza o nome de um cliente existente.
{
"name": "Novo Nome do Cliente"
}/customers/{uuid}Deletar Cliente
Remove um cliente e todas as suas integrações associadas.
{
"success": true,
"message": "Record successfully removed"
}/customers/{uuid}/update-payment-statusLegadoAtualizar Status de Pagamento
Marca um cliente como pagante ou não pagante. Funciona apenas para os merchants com cobrança por cliente (legado); nos demais, todo cliente já nasce pagante.
{
"is_paying": true
}/customers/settingsConfigurações do Cliente
Retorna as configurações e integrações ativas de um cliente específico, utilizando o x-customer-token no header.
{
"customer": {
"uuid": "073c1e15-94d0-49b3-9861-a0e9b7ffb06c",
"name": "Reportei Customer 1",
"created_at": "2024-09-24 19:06:08",
"updated_at": "2024-09-24 19:06:08",
"trial_ends_at": "2024-10-08 17:15:12",
"is_paying": true,
"merchant": "Reportei",
"integrations": [
{
"uuid": "6110b666-0327-4a81-b48e-9feb34d72f78",
"integration": "Instagram Business",
"name": "reportei",
"slug": "instagram_business",
"status": "active"
}
]
}
}Customer Integrations
Gerencie as conexões das plataformas de redes sociais e marketing dos seus clientes. Utilize o x-customer-token para acessar estes endpoints.
Status da Integração
Cada integração de cliente possui um status que indica sua validade e conexão. Verifique o campo status para entender o estado da conexão.
/customer-integrations/sessionCriar Sessão de Integração
Inicia um fluxo para que o cliente possa conectar novas contas de redes sociais ou gerenciar as existentes. Retorna um link único para a interface de integração.
Todos os campos são opcionais.
| Campo | Descrição |
|---|---|
| redirect_url | URL de redirecionamento após a integração |
| locale | Idioma da interface de integração (ex: pt_BR, en, es, fr) |
| limits | Array de objetos para controlar o comportamento da sessão (veja os limites disponíveis abaixo) |
| limit_reached_url | URL para redirecionar quando o limite de integrações for atingido |
| expires_in_minutes | Tempo de expiração da sessão em minutos (mínimo: 1) |
| close_on_finish | Se true, fecha a janela ao finalizar a integração |
| enable_multi_account_selection | Permite que o usuário selecione múltiplas contas na mesma sessão |
| toolkit_slug | Abre a sessão num toolkit, pelo slug de GET /toolkits, em vez do catálogo de plataformas. Não aceita source_slug junto. Ao terminar, o cliente volta para a redirect_url com ?customer_toolkit={uuid}. Veja a seção Toolkits. |
Limits disponíveis
O campo limits recebe um array de objetos, cada um com name e value. É possível combinar múltiplos limites na mesma sessão.
integration_countintegerbloqueiaLimita o número total de integrações que o customer pode conectar nesta sessão.
{ "name": "integration_count", "value": 5 }same_type_integrationintegerbloqueiaLimita quantas integrações do mesmo tipo (ex: Instagram Business) o customer pode conectar.
{ "name": "same_type_integration", "value": 2 }same_integration_across_customersintegerbloqueiaLegado, só para os merchants com cobrança por cliente: limita quantos customers não pagantes do mesmo merchant podem conectar a mesma conta.
{ "name": "same_integration_across_customers", "value": 1 }available_integrationsarray de slugsfiltro de UIFiltra quais integrações são exibidas na tela de conexão. Não bloqueia, apenas controla a visibilidade. Se omitido, todas as integrações do merchant são exibidas.
{ "name": "available_integrations", "value": ["instagram_business", "facebook_ads", "google_analytics_4"] }Exemplo combinando múltiplos limites
{
"redirect_url": "https://sua-aplicacao.com/dashboard",
"limits": [
{ "name": "integration_count", "value": 3 },
{ "name": "available_integrations", "value": ["instagram_business", "facebook_ads"] }
],
"limit_reached_url": "https://sua-aplicacao.com/upgrade"
}Sessão expirável
O link da sessão é válido por 5 minutos. Acesse o session_link para iniciar o processo de integração do cliente.
{
"redirect_url": "https://sua-aplicacao.com/dashboard",
"locale": "pt_BR",
"limits": {},
"limit_reached_url": "https://sua-aplicacao.com/limite",
"expires_in_minutes": 30,
"close_on_finish": true,
"enable_multi_account_selection": false
}/customer-integrations/{uuid}Obter Integração
Retorna detalhes de uma integração específica do cliente.
{
"customer_integration": {
"uuid": "4addc0da-8583-4dbb-a0e9-c7e2e8470f50",
"source_name": "reportei",
"status": "active",
"created_at": "2024-09-24 17:08:08",
"updated_at": "2024-09-24 17:08:08",
"integration": {
"name": "Instagram Business",
"slug": "instagram_business"
}
}
}/customer-integrations/{uuid}?session_id={uuid}Deletar Integração
Remove uma integração específica do cliente. Requer o UUID da integração e o UUID de uma sessão ativa.
{
"success": true,
"message": "Record successfully removed"
}Metrics
Consulte métricas de integrações conectadas. Descubra as métricas que cada plataforma oferece em GET /metrics/{integration_slug}, teste a coleta no Connect Explorer e use /metrics/get-data para obter os dados programaticamente.
Connect Explorer
O Connect Explorer é uma ferramenta interativa que permite validar seus tokens, explorar as métricas disponíveis por integração e testar consultas à API, tudo sem escrever código. Ideal para descobrir quais métricas e dimensões estão disponíveis antes de implementar a integração.
Valide seus tokens
Acesse o Explorer, insira seu Bearer Token (merchant) e x-customer-token (customer) para validar a conexão e carregar as integrações disponíveis.

Explore métricas disponíveis
Selecione uma integração e navegue pelo catálogo completo de métricas, organizadas por tipo (Numbers, Charts, Tables). Customize métricas e dimensões conforme necessário.

Teste a consulta e copie o payload
Defina o período desejado, execute a consulta e visualize a resposta da API em tempo real. Copie o payload gerado para usar diretamente nos endpoints /metrics/get-data ou /metrics/get-data-async.

Estrutura de Métricas
Cada métrica no array metrics possui a seguinte estrutura:
| Campo | Descrição |
|---|---|
| id | Identificador único para a métrica. Use qualquer UUID ou string única. |
| reference_key | Nome geral da métrica, ex: ig:story_replies (respostas de stories do Instagram). Os disponíveis por plataforma vêm de GET /metrics/{integration_slug}. |
| metrics | Array com os valores específicos de métricas solicitados, ex: ["replies"]. |
| dimensionsOpcional | Detalhamento adicional da métrica, ex: ["stories", "date", "device"]. |
| component | Tipo da métrica, que altera a estrutura da resposta final. |
| entity_idOpcional | Junto com entity_type, informa o ID e o tipo da entidade para filtrar resultados (ad, campaign ou adset). |
Componentes disponíveis
| component | Descrição |
|---|---|
| number_v1 | Retorna um único valor numérico. |
| datatable_v1 | Retorna dados tabulares. |
| chart_v1 | Retorna dados formatados para gráficos. |
Importante
Dependendo da rede da integração do cliente, algumas combinações de métricas e dimensões podem não ser compatíveis.
/metrics/{integration_slug}Listar Métricas Disponíveis
Retorna todas as métricas que uma plataforma oferece, com reference_key, component, metrics e dimensions padrão, além dos valores aceitos em meta.setup. Requer apenas o Bearer Token.
Parâmetros obrigatórios
| Nome | Descrição |
|---|---|
| integration_slug | Slug da plataforma, como devolvido por GET /integrations (ex.: facebook_ads) |
Parâmetros opcionais
| Nome | Descrição |
|---|---|
| language | Idioma dos títulos e labels: en (padrão), pt_br, es, fr, it, de |
Cada item de data já é uma métrica válida para o array metrics de /metrics/get-data: copie id, reference_key, component, metrics e dimensions. Para pedir outro valor ou detalhamento, troque metrics e dimensions por chaves de meta.setup. O catálogo muda raramente e é cacheado pelo Connect: busque uma vez e guarde do seu lado.
{
"data": [
{
"id": "c45d148c-0d07-4473-b7d7-f886c8b56884",
"reference_key": "fb_ads:spend",
"component": "number_v1",
"metrics": ["spend"],
"dimensions": [],
"references": {
"title": "Valor investido",
"description": "O valor total estimado que você gastou na sua campanha, conjunto de anúncios ou anúncio durante seu agendamento.",
"tags": { "cost": "pago", "history": "retroativo" }
}
},
{
"id": "80e06171-759e-472e-9cff-25ae8e91e1e0",
"reference_key": "fb_ads:insights_by_campaign",
"component": "datatable_v1",
"metrics": ["results", "cost_per_results", "spend", "reach", "impressions", "ctr", "cpc", "cpm", "frequency"],
"dimensions": [{ "field": "campaign" }],
"references": {
"title": "Campanhas em destaque",
"description": "Campanhas que tiveram o maior impacto em sua audiência, no período analisado.",
"tags": { "cost": "pago", "history": "retroativo" }
}
}
],
"meta": {
"integration_slug": "facebook_ads",
"language": "pt_br",
"setup": {
"metrics": {
"spend": { "label": "Valor investido" },
"results": { "label": "Resultados" },
"reach": { "label": "Alcance" },
"impressions": { "label": "Impressões" }
},
"dimensions": {
"campaign": { "label": "Nome da Campanha" },
"publisher_platform": { "label": "Plataforma" },
"date": { "label": "Data" }
}
}
}
}/metrics/get-dataObter Dados de Métricas
Retorna um objeto com os valores das métricas solicitadas de uma integração de cliente.
Parâmetros obrigatórios
| Nome | Descrição |
|---|---|
| customer_integration | UUID da integração do cliente |
| start | Data de início da análise (ISO 8601) |
| end | Data de fim da análise (ISO 8601) |
| metrics | Array contendo a estrutura de métricas |
Parâmetros opcionais
| Nome | Descrição |
|---|---|
| comparison_start | Data de início da comparação (ISO 8601) |
| comparison_end | Data de fim da comparação (ISO 8601) |
{
"customer_integration": "4addc0da-8583-4dbb-a0e9-c7e2e8470f50",
"start": "2024-01-01",
"end": "2024-09-01",
"metrics": [
{
"id": "f35a44ce-dac5-4188-aace-5c44a8952176",
"reference_key": "ig:story_replies",
"component": "number_v1",
"metrics": ["replies"],
"dimensions": ["stories"]
},
{
"id": "3051ed66-a05c-462a-aece-e1d900e78b02",
"reference_key": "ig:followers_gender",
"component": "chart_v1",
"metrics": ["followers"],
"dimensions": ["gender"]
},
{
"id": "b3a949ac-f6ae-4bd1-bb90-1e8e0cbe8ed5",
"reference_key": "ig:clicks_breakdown",
"component": "datatable_v1",
"metrics": ["count", "ctr"],
"dimensions": ["clicks_breakdown"]
}
]
}/metrics/get-data-asyncObter Dados de Métricas (Assíncrono)
Inicia um processo para coletar os dados de métricas solicitados e envia os resultados de forma assíncrona para a metrics_webhook_url fornecida.
Parâmetros obrigatórios
| Nome | Descrição |
|---|---|
| metrics_webhook_url | URL que receberá o payload de métricas |
| customer_integration | UUID da integração do cliente |
| start | Data de início da análise (ISO 8601) |
| end | Data de fim da análise (ISO 8601) |
| metrics | Array contendo a estrutura de métricas |
Parâmetros opcionais
| Nome | Descrição |
|---|---|
| comparison_start | Data de início da comparação (ISO 8601) |
| comparison_end | Data de fim da comparação (ISO 8601) |
A estrutura do corpo da requisição é idêntica ao endpoint /metrics/get-data, e o payload enviado para a metrics_webhook_url segue o mesmo formato da resposta daquele endpoint. A chamada retorna 200 OK assim que o processo de coleta é iniciado.
{
"metrics_webhook_url": "https://sua-aplicacao.com/webhook/metrics",
"customer_integration": "4addc0da-8583-4dbb-a0e9-c7e2e8470f50",
"start": "2024-01-01",
"end": "2024-09-01",
"metrics": [
{
"id": "f35a44ce-dac5-4188-aace-5c44a8952176",
"reference_key": "ig:story_replies",
"component": "number_v1",
"metrics": ["replies"],
"dimensions": ["stories"]
},
{
"id": "3051ed66-a05c-462a-aece-e1d900e78b02",
"reference_key": "ig:followers_gender",
"component": "chart_v1",
"metrics": ["followers"],
"dimensions": ["gender"]
},
{
"id": "b3a949ac-f6ae-4bd1-bb90-1e8e0cbe8ed5",
"reference_key": "ig:clicks_breakdown",
"component": "datatable_v1",
"metrics": ["count", "ctr"],
"dimensions": ["clicks_breakdown"]
}
]
}Advertising
Crie e gerencie anúncios na conta de anúncios do seu cliente sem implementar a API da integração. Hoje apenas Meta (`facebook_ads`). As rotas de um cliente exigem o Bearer do merchant, o `x-customer-token` do cliente e a conexão em `?customer_integration=`, sempre na query. A conta de anúncios sai da conexão; `ad_account_id` no corpo é ignorado. O panorama de todos os clientes, em `/merchants/ads/*`, pede só o Bearer e fica na seção Merchants.
IDs nos endpoints
Onde um endpoint de ads pede um id, vale tanto o `uuid` do Connect quanto o `id` do objeto, o numérico que a plataforma de anúncios deu à campanha, ao conjunto ou ao anúncio (na Meta, o mesmo do Gerenciador de Anúncios). Isso inclui o `{id}` da rota, o `id` de um nível no corpo (`campaign: { "id": … }`) e filtros como `campaign_id`. O `id` funciona para qualquer objeto da conta. O `uuid` só funciona para os que o Connect guarda. As respostas trazem os dois: `id` é o do objeto e `uuid` é o do Connect, que vem `null` quando o objeto não está no Connect.
Campos de cada integração
Em cada nível, os campos que toda integração de anúncios tem ficam no topo (`name`, `status`, `budget`, `schedule`, `targeting`…). O que é só de uma integração vai num bloco com o nome do `integration_slug` dela. Hoje esse bloco é `facebook_ads: { … }`, com objetivo, otimização, lance, posicionamentos e interesses. O conteúdo do bloco é da integração e pode ganhar campos novos. Outra integração terá o próprio bloco, com campos diferentes (por exemplo, `google_ads: { … }`), e os campos do topo continuam os mesmos. Os campos e valores aceitos em cada bloco estão em `GET /ads/options?integration_slug=`.
Esta é a única superfície que gasta dinheiro de verdade
Tudo que uma escrita cria nasce `paused`. Quando você pede `status: active` no `POST /ads`, a ativação é o último passo, depois que a árvore inteira existe. Uma falha no meio nunca deixa nada gastando.
Se a criação quebrar no meio, o que já foi criado é desfeito em ordem inversa. O que não for possível desfazer volta em `created`, com nível e id, para você remover.
Toda escrita é idempotente pelo header `Idempotency-Key` (obrigatório nas criações). Repetir com a mesma chave e o mesmo pedido devolve a primeira resposta com `Idempotent-Replay: true`; depois de uma falha, a repetição retoma de onde parou em vez de criar uma segunda campanha. Gere a chave uma vez por operação, antes da primeira tentativa, e reenvie a mesma em toda repetição: uma chave nova a cada tentativa cria outra campanha.
O Connect guarda o que ele cria
As listagens de campanhas, conjuntos e anúncios leem do banco do Connect por padrão (`source=connect`): o que o Connect criou ou já mexeu nesta conta, com `uuid` e `synced_at`. Uma conta que já tinha campanhas lista vazio até a primeira escrita pelo Connect. Use `source=external` para ler a conta ao vivo. Qualquer escrita aceita o id de qualquer objeto da conta, e o objeto entra no banco nessa hora (`origin: imported`). A paginação é a padrão: `page` e `per_page`. Cada objeto traz `customer_uuid` e `customer_integration_uuid`: o cliente dono da conta e a conexão pela qual o Connect a alcançou por último. Os dois são `null` quando o objeto não está no banco do Connect, e a conexão também fica `null` depois de apagada.
Status efetivo e problemas
`status` é o que você pediu (`active` ou `paused`). `effective_status` é o que a plataforma aplica de fato, somando o objeto, os níveis acima dele e a revisão: `pending_review` em análise, `disapproved` reprovado, `with_issues` com problemas, `campaign_paused` ou `adset_paused` quando um nível acima está pausado. Nas listagens com `source=connect`, o filtro `effective_status` traz, por exemplo, só os anúncios reprovados; com `source=external`, ele responde 422.
`issues` diz por que a plataforma segura o objeto: uma lista de `{ code, message }` com os problemas de entrega em qualquer nível e, num anúncio, o motivo da reprovação (`code: ad_review`). Lista vazia quando não há nada a relatar; `null` quando o estado ainda não foi lido. `refresh=true` relê `status`, `effective_status`, `issues`, `name` e `synced_at` na integração.
Opções
Você fala sempre o mesmo vocabulário em snake_case e o Connect traduz para a integração: `traffic` vira `OUTCOME_TRAFFIC`, e um orçamento de 50 vira a unidade mínima da moeda da conta. Em cada nível, o que toda integração de anúncios tem fica no topo, e o que é só da Meta vai em `facebook_ads`. Leia daqui o que é aceito, em vez de adivinhar.
/ads/optionsVocabulário aceito
Publica, nível a nível, o que a integração aceita: os valores dos campos comuns, onde o orçamento pode ficar e os campos do bloco `facebook_ads`. Não depende de conexão: passe `integration_slug`.
{
"data": {
"integration_slug": "facebook_ads",
"budget": {
"levels": [
"campaign",
"adset"
],
"types": [
"daily",
"lifetime"
]
},
"common": {
"status": [
"active",
"paused"
],
"format": [
"image",
"video"
],
"call_to_action": [
"shop_now",
"learn_more",
"sign_up"
],
"targeting": [
"countries",
"age_min",
"age_max",
"genders"
],
"genders": [
"all",
"male",
"female"
]
},
"facebook_ads": {
"campaign": {
"objective": [
"traffic",
"engagement",
"leads",
"sales",
"awareness",
"app_promotion"
],
"fields": [
"objective",
"special_ad_categories",
"spend_cap"
]
},
"adset": {
"optimization_goal": [
"link_clicks",
"landing_page_views",
"post_engagement"
],
"billing_event": [
"impressions",
"link_clicks"
],
"bid_strategy": [
"lowest_cost",
"bid_cap",
"cost_cap"
],
"fields": [
"optimization_goal",
"billing_event",
"bid_strategy",
"bid_amount",
"placements",
"targeting",
"promoted_object"
]
},
"ad": {
"required": [
"page_id"
],
"fields": [
"page_id",
"instagram_account_id",
"url_tags",
"tracking_specs"
]
}
}
}
}Campanhas
O nível mais alto da árvore: define o objetivo (`facebook_ads.objective`) e, no modo CBO, o orçamento. Listar, criar, detalhar, atualizar, duplicar e remover. Remover uma campanha leva junto tudo que está abaixo dela.
/ads/campaignsListar campanhas
Do banco do Connect por padrão; com `source=external`, a conta ao vivo. Filtros `status`, `effective_status` e `include_deleted` (os dois últimos só com `source=connect`); paginação por `page` e `per_page`.
{
"data": [
{
"id": "120250000000000001",
"uuid": "9a1d3f52-7c1e-4c55-9b0e-5c3f1c6a2e10",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"name": "Black Friday",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": {
"amount": 50,
"type": "daily",
"level": "campaign",
"currency": "BRL"
},
"adsets_count": 1,
"facebook_ads": {
"objective": "traffic",
"spend_cap": null,
"bid_strategy": "lowest_cost"
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/ads/campaigns?page=1",
"last": "https://connect.reportei.com/api/ads/campaigns?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/ads/campaigns",
"per_page": 15,
"to": 1,
"total": 1
}
}/ads/campaignsCriar campanha
O objetivo vai em `facebook_ads.objective`; com `budget`, a campanha é CBO. Sem `status` nasce pausada. Exige `Idempotency-Key`.
{
"name": "Black Friday",
"budget": {
"amount": 50,
"type": "daily"
},
"facebook_ads": {
"objective": "traffic",
"special_ad_categories": []
}
}/ads/campaigns/{id}Detalhar campanha
Pelo `id` da campanha ou pelo `uuid` do Connect. `refresh=true` relê `status`, `effective_status`, `issues` e `name` na integração; `source=external` lê ao vivo.
{
"data": {
"id": "120250000000000001",
"uuid": "9a1d3f52-7c1e-4c55-9b0e-5c3f1c6a2e10",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"name": "Black Friday",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": {
"amount": 50,
"type": "daily",
"level": "campaign",
"currency": "BRL"
},
"adsets_count": 1,
"facebook_ads": {
"objective": "traffic",
"spend_cap": null,
"bid_strategy": "lowest_cost"
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
}/ads/campaigns/{id}Atualizar campanha
Vale para qualquer campanha da conta; uma criada fora entra no banco nesta escrita. Orçamento numa campanha ABO volta 409 `budget_level_mismatch`.
{
"status": "active",
"budget": {
"amount": 80
}
}/ads/campaigns/{id}/duplicateDuplicar campanha
Copia a campanha sem os conjuntos e anúncios. A cópia mantém objetivo, nível do orçamento (CBO/ABO) e categorias especiais; você manda o `name` e, se quiser, `status` e `budget`. Um `budget` numa campanha ABO volta 409 `budget_level_mismatch`, antes de copiar. Exige `Idempotency-Key`.
A cópia nasce pausada e, com `status: active`, é ativada no último passo. Se a integração recusar o orçamento ou a agenda, a cópia é apagada, e repetir com a mesma `Idempotency-Key` copia de novo. Se só a ativação falhar, a cópia fica, pausada e completa, e volta em `created`; repetir com a mesma chave só ativa.
{
"name": "Black Friday | 02",
"budget": {
"amount": 150,
"type": "daily"
}
}/ads/campaigns/{id}Remover campanha
Remove a campanha e, em cascata, os conjuntos e anúncios abaixo dela, na integração e no Connect.
{
"data": {
"id": "120250000000000001",
"deleted": true
}
}Conjuntos de anúncios
Onde moram público, posicionamento, agenda e, no modo ABO, o orçamento. O `PATCH` de público é substituição total na integração, então o Connect lê o conjunto atual e mescla o que você mandou: mandar parte de um público não apaga o resto. Duplicar copia o conjunto sem os anúncios, para a mesma campanha ou outra.
/ads/adsetsListar conjuntos
Mesmas regras das campanhas. `campaign_id` aceita o `id` da campanha ou o `uuid`.
{
"data": [
{
"id": "120250000000000002",
"uuid": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"name": "BR 25-45",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": null,
"schedule": {
"start": "2026-11-20T00:00:00-0300",
"end": null
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45,
"genders": [
"all"
]
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"bid_strategy": null,
"bid_amount": null,
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"custom_audiences": [],
"excluded_custom_audiences": [],
"advantage_audience": false,
"raw": {}
}
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/ads/adsets?page=1",
"last": "https://connect.reportei.com/api/ads/adsets?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/ads/adsets",
"per_page": 15,
"to": 1,
"total": 1
}
}/ads/adsetsCriar conjunto
Público comum em `targeting`; meta de otimização, lance, posicionamentos e interesses em `facebook_ads`. Orçamento sob uma campanha CBO volta 409.
{
"campaign_id": "120250000000000001",
"budget": {
"amount": 20,
"type": "daily"
},
"name": "BR 25-45",
"schedule": {
"start": "2026-11-20T00:00:00-03:00"
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"advantage_audience": false
}
}
}/ads/adsets/{id}Detalhar conjunto
Um conjunto pelo id ou `uuid`, com público e agenda no vocabulário do Connect.
{
"data": {
"id": "120250000000000002",
"uuid": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"name": "BR 25-45",
"status": "paused",
"effective_status": "paused",
"issues": [],
"budget": null,
"schedule": {
"start": "2026-11-20T00:00:00-0300",
"end": null
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45,
"genders": [
"all"
]
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"bid_strategy": null,
"bid_amount": null,
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"custom_audiences": [],
"excluded_custom_audiences": [],
"advantage_audience": false,
"raw": {}
}
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
}/ads/adsets/{id}Atualizar conjunto
Alteração parcial. O público é mesclado com o atual, então mandar um campo não apaga os outros.
{
"targeting": {
"age_min": 30
},
"facebook_ads": {
"placements": {
"publisher_platforms": [
"instagram"
]
}
}
}/ads/adsets/{id}/duplicateDuplicar conjunto
Copia o conjunto sem os anúncios, para a campanha de `campaign_id` (`id` ou `uuid`) ou, sem ele, para a campanha do modelo. Público, posicionamentos, otimização e objeto promovido vêm do modelo; `name`, `status`, `budget` e `schedule` podem mudar. Um `budget` com a campanha de destino em CBO volta 409 `budget_level_mismatch`, antes de copiar. Exige `Idempotency-Key`.
A cópia nasce pausada e, com `status: active`, é ativada no último passo. Se a integração recusar o orçamento ou a agenda, a cópia é apagada, e repetir com a mesma `Idempotency-Key` copia de novo. Se só a ativação falhar, a cópia fica, pausada e completa, e volta em `created`; repetir com a mesma chave só ativa.
{
"name": "Público quente | 02",
"campaign_id": "120250000000000001",
"schedule": {
"start": "2026-11-20T00:00:00-03:00"
}
}/ads/adsets/{id}Remover conjunto
Remove o conjunto e os anúncios dentro dele.
{
"data": {
"id": "120250000000000002",
"deleted": true
}
}Anúncios
O criativo e o que ele mostra. O `POST /ads` cria o anúncio e o que faltar acima dele, em uma chamada; `POST /ads/boost` faz o mesmo promovendo um post existente; `POST /ads/validate` ensaia sem criar.
| Forma | O que a dispara | O que cria |
|---|---|---|
| Completa | campaign + adset + ad | |
| Sob campanha existente | campaign: { id } | adset + ad |
| Anexar a conjunto existente | adset: { id } | ad |
| Impulsionar post orgânico | POST /ads/boost | a mesma árvore, com o post no lugar do criativo |
Nada é deduzido do corpo: cada nível usa um existente (`{ "id": … }`) ou descreve o que será criado. Um nível com `id` não aceita outro campo, chaves desconhecidas são recusadas com 422, e o orçamento fica na campanha (CBO) ou no conjunto (ABO), nunca nos dois. O que é só da Meta vai no bloco `facebook_ads` de cada nível.
/adsListar anúncios
Do banco do Connect por padrão. Filtros `campaign_id` e `adset_id` aceitam id ou `uuid`.
{
"data": [
{
"id": "120250000000000003",
"uuid": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"adset_id": "120250000000000002",
"name": "Black Friday, imagem",
"status": "paused",
"effective_status": "paused",
"issues": [],
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"description": null,
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"thumbnail_url": null,
"facebook_ads": {
"creative_id": "120250000000000004",
"page_id": "1068595869664481",
"instagram_account_id": null,
"post_id": null
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
],
"links": {
"first": "https://connect.reportei.com/api/ads?page=1",
"last": "https://connect.reportei.com/api/ads?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/ads",
"per_page": 15,
"to": 1,
"total": 1
}
}/adsCriar a árvore completa
Cria campanha, conjunto e anúncio em uma chamada. Tudo nasce pausado e, com `status: active`, é ativado no último passo. Exige `Idempotency-Key`.
{
"name": "Black Friday, imagem",
"status": "active",
"campaign": {
"name": "Black Friday",
"budget": {
"amount": 50,
"type": "daily"
},
"facebook_ads": {
"objective": "traffic",
"special_ad_categories": []
}
},
"adset": {
"name": "BR 25-45",
"schedule": {
"start": "2026-11-20T00:00:00-03:00"
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"advantage_audience": false
}
}
},
"ad": {
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"media": [
{
"type": "image",
"url": "https://cdn.loja.com/bf.jpg"
}
],
"facebook_ads": {
"page_id": "1068595869664481"
}
}
}/adsCriar sob campanha existente
`campaign: { "id": … }` usa uma campanha que já existe; o conjunto e o anúncio são criados. O exemplo é um anúncio de vídeo: `thumbnail_url` é obrigatório.
{
"name": "Black Friday, vídeo",
"campaign": {
"id": "120250000000000001"
},
"adset": {
"name": "BR 25-45",
"schedule": {
"start": "2026-11-20T00:00:00-03:00"
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"advantage_audience": false
}
}
},
"ad": {
"format": "video",
"link_url": "https://loja.com/bf",
"media": [
{
"type": "video",
"url": "https://cdn.loja.com/bf.mp4",
"thumbnail_url": "https://cdn.loja.com/bf.jpg"
}
],
"facebook_ads": {
"page_id": "1068595869664481"
}
}
}/adsAnexar a conjunto existente
`adset: { "id": … }` cria só o anúncio. A campanha não pode ser enviada junto.
{
"name": "Black Friday, variação",
"adset": {
"id": "120250000000000002"
},
"ad": {
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"media": [
{
"type": "image",
"url": "https://cdn.loja.com/bf.jpg"
}
],
"facebook_ads": {
"page_id": "1068595869664481"
}
}
}/adsCriar anúncio com versão de feed e de story
O mesmo criativo com dois arquivos: um em `feed` (Feed do Facebook e do Instagram) e outro em `story` (Stories e Reels), cada um com `placements`. Textos, link e botão valem para os dois. Com dois arquivos, cada um é nomeado pelo que a conta já tem, o `hash` da imagem ou o `id` do vídeo de `POST /ads/facebook_ads/media`, nunca por `url`. Os dois têm o tipo do `format`, e vídeo leva `thumbnail_url`.
{
"name": "Black Friday, feed e story",
"adset": {
"id": "120250000000000002"
},
"ad": {
"format": "video",
"headline": "Black Friday",
"body": "Até 50% off",
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"media": [
{
"type": "video",
"id": "120210000000001",
"thumbnail_url": "https://cdn.loja.com/bf-feed.jpg",
"placements": ["feed"]
},
{
"type": "video",
"id": "120210000000002",
"thumbnail_url": "https://cdn.loja.com/bf-story.jpg",
"placements": ["story"]
}
],
"facebook_ads": {
"page_id": "1068595869664481"
}
}
}/ads/boostImpulsionar um post orgânico
A mesma árvore, com `ad.post_id` no lugar do criativo. Exige `facebook_ads.instagram_account_id`, que é como a posse do post é verificada. Textos e mídia são recusados.
{
"name": "Impulsionar post",
"status": "active",
"campaign": {
"facebook_ads": {
"objective": "engagement"
}
},
"adset": {
"budget": {
"amount": 20,
"type": "daily"
},
"targeting": {
"countries": [
"BR"
]
},
"facebook_ads": {
"optimization_goal": "post_engagement",
"billing_event": "impressions"
}
},
"ad": {
"post_id": "18191649508387596",
"facebook_ads": {
"instagram_account_id": "17841442920331135"
}
}
}/ads/validateValidar sem criar
O mesmo corpo do `POST /ads`. A integração valida cada nível, e nada é criado.
A integração não valida a árvore inteira: ela recusa validar um conjunto contra uma campanha que ainda não existe. Por isso cada nível que a chamada teria de criar antes volta como `skipped`, e não como erro. O dry run não precisa de `Idempotency-Key`.
{
"name": "Black Friday, imagem",
"status": "active",
"campaign": {
"name": "Black Friday",
"budget": {
"amount": 50,
"type": "daily"
},
"facebook_ads": {
"objective": "traffic",
"special_ad_categories": []
}
},
"adset": {
"name": "BR 25-45",
"schedule": {
"start": "2026-11-20T00:00:00-03:00"
},
"targeting": {
"countries": [
"BR"
],
"age_min": 25,
"age_max": 45
},
"facebook_ads": {
"optimization_goal": "link_clicks",
"billing_event": "impressions",
"placements": {
"publisher_platforms": [
"facebook",
"instagram"
]
},
"targeting": {
"interests": [
{
"id": "6003139266461",
"name": "Moda"
}
],
"advantage_audience": false
}
}
},
"ad": {
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"media": [
{
"type": "image",
"url": "https://cdn.loja.com/bf.jpg"
}
],
"facebook_ads": {
"page_id": "1068595869664481"
}
}
}/ads/{id}Detalhar anúncio
Um anúncio pelo id ou `uuid`.
{
"data": {
"id": "120250000000000003",
"uuid": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"customer_integration_uuid": "5d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f90",
"origin": "created",
"synced_at": "2026-11-19T10:00:00-03:00",
"deleted_at": null,
"campaign_id": "120250000000000001",
"adset_id": "120250000000000002",
"name": "Black Friday, imagem",
"status": "paused",
"effective_status": "paused",
"issues": [],
"format": "image",
"headline": "Black Friday",
"body": "Até 50% off",
"description": null,
"link_url": "https://loja.com/bf",
"call_to_action": "shop_now",
"thumbnail_url": null,
"facebook_ads": {
"creative_id": "120250000000000004",
"page_id": "1068595869664481",
"instagram_account_id": null,
"post_id": null
},
"created_at": "2026-11-19T10:00:00-0300",
"updated_at": "2026-11-19T10:00:00-0300"
}
}/ads/{id}Atualizar anúncio
Nome, status e textos, num corpo plano. Mudar um texto troca o criativo e manda o anúncio para revisão; num anúncio impulsionado, o criativo não pode ser trocado.
{
"headline": "Última chance",
"description": null
}/ads/{id}/duplicateDuplicar um anúncio
Copia um anúncio existente. A cópia nasce pausada, mesmo que o original esteja ativo.
{
"name": "Black Friday, cópia"
}/ads/{id}Remover anúncio
Remove apenas o anúncio; o conjunto e a campanha continuam de pé.
{
"data": {
"id": "120250000000000003",
"deleted": true
}
}Mídia do anúncio
Entregue o arquivo à conta de anúncios uma vez e reaproveite em quantos anúncios quiser, pelo `hash` da imagem ou pelo `id` do vídeo. É assim que um criativo com versão de feed e de story nomeia cada arquivo. O Connect não guarda nada: guarde você o `hash` ou o `id`.
/ads/facebook_ads/mediaEntregar arquivo à conta
Manda à conta uma imagem (JPEG ou PNG) ou um vídeo pela `url`, um endereço https público, como o devolvido por `POST /media`. A imagem volta `ready` com o `hash`; o vídeo volta `processing` com o `id`. `name` é o nome na biblioteca da conta; sem ele, vale o nome do arquivo na URL. Exige `Idempotency-Key`.
{
"type": "video",
"url": "https://cdn.loja.com/bf-feed.mp4"
}/ads/facebook_ads/videos/{id}Processamento de um vídeo
Diz se um vídeo entregue está `processing`, `ready` ou `failed`. Use o `id` num anúncio só com `ready`. `thumbnail_url` é um quadro escolhido pela rede, que serve de miniatura no anúncio. Só responde por vídeos que esta conta recebeu por `POST /ads/facebook_ads/media`: qualquer outro `id` é 404.
{
"data": {
"id": "120210000000001",
"status": "ready",
"thumbnail_url": "https://scontent.xx.fbcdn.net/v/frame.jpg"
}
}Descoberta
Os ids que um anúncio precisa não são para o seu cliente copiar à mão do Gerenciador de Anúncios. Estes endpoints devolvem o que a conta de anúncios tem, já escopado nela. O que é só da Meta fica em `/ads/facebook_ads/*`.
/ads/facebook_ads/pagesPáginas
As páginas do Facebook que esta conta de anúncios pode usar para assinar um anúncio.
{
"data": [
{ "id": "1068595869664481", "name": "Loja Exemplo", "picture_url": "https://…", "can_post": true,
"instagram_account_id": "17841442920331135" }
]
}/ads/facebook_ads/instagram-accountsContas do Instagram
As contas do Instagram vinculadas à conta de anúncios.
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
"https://connect.reportei.com/api/ads/facebook_ads/instagram-accounts?customer_integration=$CONNECTION_UUID"/ads/facebook_ads/postsPosts para impulsionar
O conteúdo orgânico que pode ser impulsionado. Por padrão vem do Instagram, que é o que o impulsionamento consome.
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
"https://connect.reportei.com/api/ads/facebook_ads/posts?customer_integration=$CONNECTION_UUID&source=instagram&instagram_account_id=17841442920331135"/ads/facebook_ads/pixelsPixels
Os pixels da conta, para medir conversão.
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
"https://connect.reportei.com/api/ads/facebook_ads/pixels?customer_integration=$CONNECTION_UUID"/ads/facebook_ads/pixels/{id}/eventsEventos de um pixel
Os eventos que um pixel já registrou, com a contagem de cada um.
{
"data": [
{ "event_name": "Purchase", "count": 1823 },
{ "event_name": "AddToCart", "count": 9120 }
]
}/ads/facebook_ads/custom-conversionsConversões personalizadas
As conversões da conta. Uma conversão excluída é arquivada pela integração e não aparece aqui.
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
"https://connect.reportei.com/api/ads/facebook_ads/custom-conversions?customer_integration=$CONNECTION_UUID"/ads/facebook_ads/custom-conversionsCriar conversão personalizada
A regra vem de você (`rule.url_contains` ou `rule.event`) e o `pixel_id` é obrigatório. Exige `Idempotency-Key`.
{
"name": "Compra acima de R$ 200",
"pixel_id": "1234567890",
"event_type": "purchase",
"rule": { "url_contains": "/checkout/sucesso" }
}/ads/audiencesPúblicos personalizados
Os públicos que a segmentação pode usar em `facebook_ads.targeting.custom_audiences`.
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
"https://connect.reportei.com/api/ads/audiences?customer_integration=$CONNECTION_UUID"/ads/targeting/searchBuscar segmentação
Resolve um termo nos ids que a segmentação espera. Interesses e comportamentos voltam com `id`; lugares voltam com `key` e um `subtype` que diz se é país, região ou cidade.
{
"data": [
{ "id": "6003139266461", "name": "Café", "type": "interest", "audience_size": { "lower": 10000000, "upper": 50000000 } },
{ "key": "BR", "name": "Brasil", "type": "geo", "subtype": "country" }
]
}Erros desta superfície
Além dos erros comuns da API, estes são próprios dos endpoints de anúncios.
| Status | error_code | Descrição |
|---|---|---|
| 403 | forbidden | A conexão informada não é deste cliente. |
| 404 | ads_object_not_found | O objeto não está na conta de anúncios desta conexão, ou o `uuid` não é de um objeto que o Connect guarda. É 404 e não 403 de propósito: a resposta não confirma que o id existe em outro lugar. |
| 409 | operation_in_progress | A mesma `Idempotency-Key` ainda está rodando. Espere o `Retry-After` e repita. |
| 409 | budget_level_mismatch | O orçamento desta campanha vive no outro nível (`budget_level` diz qual). Mande-o lá. |
| 409 | ads_video_not_ready | A integração ainda está processando o vídeo. Repita com a mesma `Idempotency-Key`: o vídeo e os níveis já criados são reaproveitados. |
| 422 | validation_error | Falha de regra do corpo. `errors` diz o campo e, para enums, `supported_values` diz o que era aceito. |
| 422 | idempotency_key_required | Uma criação sem o header `Idempotency-Key`. |
| 422 | idempotency_key_reused | A mesma `Idempotency-Key` com outro pedido. |
| 422 | ads_connection_expired | O token da conexão precisa ser renovado pelo seu cliente (`integration_status: expired`). |
| 422 | ads_unsupported_integration | A conexão existe, mas não é de uma integração de anúncios que gerenciamos. |
| 422 | boosted_creative_not_replaceable | O criativo de um anúncio impulsionado não pode ser trocado. Crie outro anúncio. |
| 4xx/5xx | ads_integration_error | A integração recusou a operação, ou não respondeu. |
Quando a integração recusa por regra de negócio, o status dela é espelhado e o `error_code` é o código dela, não `ads_integration_error`, com o detalhe em `integration_error`. Se ela não responder, o status é 502 e repetir com a mesma `Idempotency-Key` retoma de onde parou.
Mídia
Envie um arquivo uma vez e receba uma URL para usar em posts e anúncios. O Connect guarda o arquivo e devolve uma URL em file.connect.reportei.com, um domínio que as redes aceitam, inclusive as que só buscam mídia de domínio verificado. O módulo só guarda o arquivo e devolve a URL: não publica nada e não sabe para qual rede o arquivo vai. A URL não tem assinatura: quem tem o link lê o arquivo, então trate-a como segredo até publicar. Todas as rotas pedem o Bearer e o x-customer-token.
É opcional
Posts e anúncios continuam recebendo mídia por URL. Se você já hospeda o arquivo numa URL pública, continue mandando a sua URL. Use este módulo quando não tiver onde hospedar, ou quando a rede recusar o seu domínio.
Com a mídia pronta, use a url em media[].url de POST /posts e dos anúncios: Posts · Advertising
Envio único (até 8 MB)
Até 8 MB, uma requisição basta: POST /media com o arquivo em multipart/form-data, no campo file. A resposta já traz a mídia pronta e a url. Acima do teto, a resposta é 413 media_too_large_for_single_upload, e o arquivo vai em partes.
/mediaEnviar um arquivo (até 8 MB)
O arquivo inteiro numa requisição multipart/form-data, no campo file. Responde 201 com a mídia ready e a url; a repetição com a mesma Idempotency-Key responde 200 com a mesma mídia. Acima de 8 MB, 413 media_too_large_for_single_upload.
Parâmetros
| Campo | Descrição |
|---|---|
| Idempotency-Key | Header obrigatório, até 255 caracteres. Uma chave sua por arquivo: gere antes da primeira tentativa e reenvie a mesma em toda repetição. |
| file | Obrigatório. O arquivo, até 8 MB. O tipo é lido do conteúdo, não do nome. |
{
"media": {
"uuid": "8b1e4f2a-6c3d-4e5f-9a0b-1c2d3e4f5a6b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "ready",
"type": "image",
"mime_type": "image/jpeg",
"name": "verao-4x5.jpg",
"size_bytes": 482133,
"url": "https://file.connect.reportei.com/media/2f0c.../8b1e....jpg",
"expires_at": "2026-11-29T12:00:00.000000Z",
"upload": null,
"created_at": "2026-10-30T12:00:00.000000Z"
}
}Envio em partes (até 1 GB)
Acima de 8 MB, até 1 GB, o arquivo vai em partes. Serve também para arquivos pequenos. O envio fica aberto por 24 horas: o que não for completado nesse prazo é descartado.
Abra o envio com POST /media/uploads, com file_name e o size exato em bytes. A resposta traz a mídia em uploading e, em upload, o part_size (bytes de cada parte), parts (quantas são) e expires_at (até quando completar).
Envie cada parte com PUT /media/uploads/{uuid}/parts/{n}, numeradas a partir de 1: o conteúdo bruto no corpo (application/octet-stream) e o Content-Length exato. Cada parte tem part_size bytes, e a última leva o resto. A parte 1 vai primeiro, porque o tipo do arquivo é lido dela; depois, as outras vão em qualquer ordem, inclusive em paralelo.
Se o envio for interrompido, GET /media/uploads/{uuid} diz em received_parts quais partes já chegaram. Mande só as que faltam. Reenviar uma parte substitui a anterior.
Feche com POST /media/uploads/{uuid}/complete. O Connect junta as partes, confere o arquivo inteiro e responde 201 com a mídia ready e a url.
/media/uploadsAbrir um envio em partes
Para arquivos de até 1 GB. Responde 201 com a mídia em uploading e, em upload, part_size, parts e expires_at. Complete antes de upload.expires_at, 24 horas depois.
Parâmetros
| Campo | Descrição |
|---|---|
| Idempotency-Key | Header obrigatório, até 255 caracteres. Uma chave sua por arquivo: gere antes da primeira tentativa e reenvie a mesma em toda repetição. |
| file_name | Obrigatório, até 255 caracteres. O nome do arquivo, que volta em name. |
| size | Obrigatório. O tamanho exato do arquivo em bytes, de 1 a 1073741824 (1 GB). Define quantas partes o envio tem. |
{
"file_name": "reels-verao.mp4",
"size": 52428800
}Exemplo: as partes com split e curl
Corte o arquivo no part_size devolvido pelo POST /media/uploads e envie as partes em ordem. Com --data-binary, o curl manda o Content-Length certo.
# 50 MB in parts of upload.part_size (8388608 bytes): part_aa, part_ab...
split -b 8388608 reels-verao.mp4 part_
n=1
for part in part_*; do
curl -s -X PUT "https://connect.reportei.com/api/media/uploads/$MEDIA_UUID/parts/$n" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$part"
n=$((n + 1))
done/media/uploads/{uuid}/parts/{part}Enviar uma parte
O corpo é o conteúdo bruto da parte (application/octet-stream), com exatamente upload.part_size bytes, ou o resto na última, e Content-Length obrigatório. A parte 1 vai primeiro; depois, qualquer ordem, inclusive em paralelo. Reenviar uma parte substitui a anterior. Responde 204, sem corpo.
curl -s -i -X PUT https://connect.reportei.com/api/media/uploads/$MEDIA_UUID/parts/1 \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-customer-token: $CUSTOMER_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @part_aa/media/uploads/{uuid}Estado de um envio em partes
A mídia e received_parts, as partes que já chegaram, para retomar um envio interrompido sem mandar tudo de novo. Depois da parte 1, type e mime_type já vêm preenchidos.
{
"media": {
"uuid": "3c9d2e1f-7a4b-4c5d-8e6f-0a1b2c3d4e5f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "uploading",
"type": "video",
"mime_type": "video/mp4",
"name": "reels-verao.mp4",
"size_bytes": 52428800,
"url": null,
"expires_at": null,
"upload": {
"part_size": 8388608,
"parts": 7,
"expires_at": "2026-10-31T12:00:00.000000Z"
},
"created_at": "2026-10-30T12:00:00.000000Z"
},
"received_parts": [1, 2, 4]
}/media/uploads/{uuid}/completeCompletar o envio
Junta as partes, confere o arquivo inteiro e responde 201 com a mídia ready e a url. Não pede Idempotency-Key: completar de novo responde 200 com a mesma mídia. Com partes faltando, 409 media_parts_missing; um arquivo que não confere é apagado e responde 422 media_content_invalid.
{
"media": {
"uuid": "3c9d2e1f-7a4b-4c5d-8e6f-0a1b2c3d4e5f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "ready",
"type": "video",
"mime_type": "video/mp4",
"name": "reels-verao.mp4",
"size_bytes": 52428800,
"url": "https://file.connect.reportei.com/media/2f0c.../3c9d....mp4",
"expires_at": "2026-11-29T12:00:00.000000Z",
"upload": null,
"created_at": "2026-10-30T12:00:00.000000Z"
}
}A URL
A url só existe com status ready. Ela é a mesma em toda leitura até expires_at: guarde-a ou leia de novo por GET /media/{uuid}.
A mídia vale 30 dias a partir de ficar pronta. Em expires_at, a URL deixa de valer e o arquivo é apagado. Não há rota para apagar antes nem para estender o prazo: publique o post ou o anúncio dentro dele.
Consultar mídias
As mídias do cliente que ainda valem, da mais recente para a mais antiga. Uma mídia expirada responde 404 media_not_found.
/mediaListar mídias
As mídias do cliente que ainda valem, da mais recente para a mais antiga. Filtro status (uploading ou ready); per_page de 1 a 100, padrão 15.
{
"data": [
{
"uuid": "8b1e4f2a-6c3d-4e5f-9a0b-1c2d3e4f5a6b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "ready",
"type": "image",
"mime_type": "image/jpeg",
"name": "verao-4x5.jpg",
"size_bytes": 482133,
"url": "https://file.connect.reportei.com/media/2f0c.../8b1e....jpg",
"expires_at": "2026-11-29T12:00:00.000000Z",
"upload": null,
"created_at": "2026-10-30T12:00:00.000000Z"
}
],
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/media",
"per_page": 15,
"to": 1,
"total": 1
}
}/media/{uuid}Consultar uma mídia
Uma mídia do cliente e a url, que é a mesma em toda leitura até expires_at. Uma mídia expirada responde 404 media_not_found.
{
"media": {
"uuid": "8b1e4f2a-6c3d-4e5f-9a0b-1c2d3e4f5a6b",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"status": "ready",
"type": "image",
"mime_type": "image/jpeg",
"name": "verao-4x5.jpg",
"size_bytes": 482133,
"url": "https://file.connect.reportei.com/media/2f0c.../8b1e....jpg",
"expires_at": "2026-11-29T12:00:00.000000Z",
"upload": null,
"created_at": "2026-10-30T12:00:00.000000Z"
}
}Mídias de todos os clientes
Para ver as mídias de todos os seus clientes, só com o Bearer, use a rota de panorama na seção Merchants:
Sem duplicar
POST /media e POST /media/uploads exigem o header Idempotency-Key. Mesma chave e mesmo pedido: 200 com a mídia já guardada e o header Idempotent-Replay: true, sem guardar de novo. Mesma chave com outro pedido: 422 idempotency_key_reused. A chave de uma mídia que já expirou responde 404 media_not_found: use outra. O complete não pede chave, porque completar de novo responde 200 com a mesma mídia.
Tipos e limites
- Tipos aceitos: JPEG, PNG, WebP e GIF (image), MP4 e MOV (video). O tipo é lido dos bytes do arquivo; o nome e a extensão não decidem nada. O arquivo inteiro é conferido antes de ficar pronto.
- Envio único: até 8 MB.
- Envio em partes: até 1 GB.
- Imagem: até 30 MB.
- Até 20 envios em partes abertos ao mesmo tempo por cliente.
- Um volume diário de envio por conta. Acima dele, 429 media_daily_limit.
- Um envio em partes precisa ser completado em 24 horas.
- A mídia pronta vale 30 dias.
- Os limites de cada rede (tamanho, duração, proporção) não são conferidos aqui: quem recusa é a rede, no post ou no anúncio.
Erros
Além dos erros comuns da API, estas são as recusas das rotas de mídia, com message e error_code. Ramifique por error_code, nunca pela mensagem. Uma falha de validação do corpo ou da query devolve 422 com errors.
| Status | error_code | Descrição |
|---|---|---|
| 404 | media_not_found | Não há mídia com esse uuid neste cliente, ou ela já expirou. Também no POST com a Idempotency-Key de uma mídia expirada: use outra chave. |
| 409 | media_upload_in_progress | Um POST /media com a mesma Idempotency-Key ainda está guardando o arquivo. Repita em instantes. |
| 409 | media_part_one_first | Outra parte chegou antes da parte 1. Envie a parte 1 primeiro. |
| 409 | media_already_completed | Parte enviada para um envio já completado. |
| 409 | media_parts_missing | O complete foi chamado com partes faltando, listadas na mensagem. Envie as que faltam e complete de novo. |
| 411 | media_part_length_required | Parte sem Content-Length. |
| 413 | media_too_large_for_single_upload | O arquivo passa do teto do envio único. Mande em partes por POST /media/uploads. |
| 422 | media_type_not_supported | O arquivo não é JPEG, PNG, WebP, GIF, MP4 nem MOV. Nada foi guardado. |
| 422 | media_content_invalid | O arquivo não é uma imagem ou um vídeo íntegro do tipo que os primeiros bytes declaram. Nada foi guardado. |
| 422 | media_type_changed | A parte 1 foi reenviada com um arquivo de outro tipo. Para outro arquivo, abra um envio novo. |
| 422 | media_part_out_of_range | O número da parte está fora de 1 a parts. |
| 422 | media_part_size_mismatch | A parte não tem part_size bytes, ou o resto, na última. |
| 422 | media_image_too_large | Imagem acima de 30 MB. |
| 422 | idempotency_key_reused | A Idempotency-Key já foi usada com outro pedido. |
| 429 | media_too_many_uploads | O cliente já tem 20 envios em partes abertos. Complete algum ou espere expirar. |
| 429 | media_daily_limit | O volume diário de envio da conta foi atingido. Tente de novo amanhã. |
Posts
Publique posts nas contas que seus customers já conectaram ao Reportei Connect. Você envia o conteúdo e o Connect cuida de todo o processo com a rede: envia as mídias, espera o processamento, publica e executa as ações pós-publicação.
A conta de destino
Toda rota de posts diz em qual conta age de uma de duas formas, nunca as duas. Pela conexão: customer_integration na query, o mesmo uuid de GET /customer-integrations e das rotas de Advertising. Pelo nome: integration_slug e source_name no corpo (nas rotas de Pinterest e YouTube, só source_name na query), que continua valendo quando a conexão é apagada e refeita para a mesma conta. Mandar os dois é recusado com 422. Os exemplos desta seção usam a conexão.
Como funciona
- Você envia o post com POST /posts, informando a conta de destino (customer_integration na query, ou integration_slug + source_name no corpo), o formato e as mídias.
- O Connect valida o pedido, grava o post e responde 202 na hora, com o uuid e o status pending. A publicação ainda não aconteceu.
- Em segundo plano, o Connect envia as mídias para a rede, espera o processamento (vídeos podem levar alguns minutos) e publica.
- Você acompanha o resultado com GET /posts/{uuid} até o post chegar a um status final: published, partially_published ou failed.
Redes
O endpoint POST /posts é o mesmo para todas as redes: a conexão (ou o integration_slug) define a rede de destino. Cada rede tem a sua seção, com formatos, campos aceitos, códigos de erro e exemplos.
URL base: https://connect.reportei.com/api
Autenticação e Idempotência
As rotas de posts usam os mesmos headers do restante da API. O POST exige também uma chave de idempotência.
| Header | Descrição |
|---|---|
| Authorization | Bearer token do merchant. Obrigatório em todas as rotas. |
| x-customer-token | Token do customer dono da conta de destino. Define em nome de quem o post é publicado e quais posts você pode consultar. Obrigatório em todas as rotas. Sem ele, a rota responde 401. |
| Idempotency-Key | Obrigatório só no POST /posts, até 255 caracteres. Identifica a tentativa de envio: reenviar com a mesma chave nunca publica duas vezes. A chave não expira e é única por customer. |
Idempotência e duplicidade
Duas travas protegem contra posts duplicados: a Idempotency-Key protege o reenvio da mesma requisição, e a external_reference impede publicar o mesmo conteúdo duas vezes na mesma conta.
| Situação | Resposta |
|---|---|
| Chave nova. | 202 Accepted com o post criado. |
| Mesma chave e mesmo corpo, por exemplo num retry após timeout. | 200 OK com o estado atual do post e o header Idempotent-Replay: true. Nada é publicado de novo. |
| Mesma chave com corpo diferente. | 422 com error_code idempotency_key_reused. |
| Chave nova, mas já existe uma publicação em andamento para a mesma conta e external_reference. | 409 com error_code post_in_progress e o post_uuid da publicação existente. |
| Chave nova, mas a mesma conta e external_reference já foram publicadas. | 409 com error_code post_already_published e o post_uuid. |
| Chave nova, e as tentativas anteriores para a mesma conta e external_reference falharam. | 202: a nova tentativa é aceita. É assim que se reenvia um post que falhou. |
Como tentar de novo
Para repetir uma requisição que ficou sem resposta (timeout, queda de rede), reenvie o mesmo corpo com a mesma Idempotency-Key. Para tentar de novo um post que terminou em failed, corrija o problema e envie com uma Idempotency-Key nova, mantendo a external_reference.
Publique em contas conectadas como instagram (login do Instagram) ou instagram_business (conta conectada pelo Facebook). Algumas opções só funcionam em instagram_business e estão sinalizadas abaixo.
Formatos suportados
- feed: uma imagem.
- carrossel: feed com 2 a 10 mídias, imagens e vídeos misturados.
- reels: exatamente um vídeo, com capa, música, trial reels e as demais opções.
- story: 1 a 10 mídias, cada uma publicada como um story separado, na ordem enviada.
Criar post no Instagram
Grava o post e coloca a publicação na fila. A resposta sai em seguida com status 202; o resultado da publicação é consultado depois.
/postsCriar post
Valida o pedido, grava o post e coloca a publicação na fila. Responde 202 com o post em pending, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "post-2026-10-08-001",
"format": "feed",
"caption": "Coleção de verão com @parceiro_oficial #verao",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/colecao.jpg",
"alt_text": "Modelo usando a coleção de verão",
"user_tags": [
{ "username": "maria.silva", "x": 0.1667, "y": 0.2778 },
{ "username": "@Loja.Centro" }
]
}
],
"instagram": {
"collaborators": ["parceiro_oficial"],
"location_id": "106377336067638",
"first_comment": "Link na bio!",
"comments_disabled": false
}
}Corpo da requisição
| Campo | Descrição |
|---|---|
| customer_integration | Na query, não no corpo. uuid da conexão de destino, de GET /customer-integrations. Com ele, não envie integration_slug nem source_name. |
| integration_slug | Obrigatório sem customer_integration. instagram ou instagram_business. Os dois buscam a conta pelo source_name tanto entre as conectadas pelo login do Instagram quanto entre as conectadas pelo Facebook, e a resposta traz em integration_slug o tipo de conexão encontrado. |
| source_name | Obrigatório sem customer_integration. Username da conta de destino, como aparece na integração do customer. A conta precisa estar conectada e ativa. |
| external_reference | Obrigatório, até 100 caracteres. O identificador do post no seu sistema. Impede publicar o mesmo post duas vezes na mesma conta e permite listar as tentativas com GET /posts. |
| format | Obrigatório. feed, reels ou story. Feed com 2 ou mais mídias vira carrossel. |
| caption | Opcional, até 2.200 caracteres, com no máximo 30 hashtags e 20 menções (@). Não é aceita em story. |
| media | Obrigatório, lista de 1 a 10 mídias. A ordem da lista é a ordem de publicação. |
| media.*.type | Obrigatório. image ou video. |
| media.*.url | Obrigatório. URL https pública, até 2.048 caracteres, de onde o Instagram baixa o arquivo. URLs assinadas precisam continuar válidas até a publicação terminar. Sem onde hospedar o arquivo? Envie pela seção Mídia (POST /media) e use a url que ela devolve. |
| media.*.alt_text | Opcional, até 1.000 caracteres. Texto alternativo da imagem, enviado no feed e nas imagens do carrossel. |
| media.*.user_tags | Opcional. Pessoas marcadas nessa mídia: lista de { username, x, y }. Veja as regras em Marcação de pessoas. |
| Opcional. Opções específicas do Instagram. Cada formato aceita um conjunto de campos; qualquer outro é recusado com 422. |
Regras por formato
| format | media | caption | |
|---|---|---|---|
| feed | 1 imagem, ou de 2 a 10 mídias (imagens e vídeos) para carrossel | Sim | collaborators, location_id, first_comment, comments_disabled |
| reels | Exatamente 1 vídeo | Sim | collaborators, location_id, share_to_feed, cover_url, audio_id, audio_volume, video_volume, trial_graduation_strategy, first_comment, comments_disabled |
| story | 1 a 10 mídias, imagem ou vídeo | Não | collaborators |
Feed com um único vídeo é recusado: o Instagram só publica vídeo único como reels, então use format reels.
Opções do Instagram
| Campo | Descrição |
|---|---|
| collaborators | Lista de até 5 usernames convidados como colaboradores. O post aparece também no perfil de quem aceitar o convite. |
| location_id | ID de um local do Facebook (página com endereço) para marcar a localização. |
| share_to_feed | Reels: exibe o reels também no feed do perfil. Padrão true. |
| cover_url | Reels: URL https da imagem de capa. |
| audio_id | Reels: ID de uma música da biblioteca do Instagram. |
| audio_volume | Reels: volume da música, de 0 a 100. Exige audio_id. |
| video_volume | Reels: volume do som original do vídeo, de 0 a 100. Exige audio_id. |
| trial_graduation_strategy | Reels: publica como trial reel, mostrado primeiro para quem não segue a conta. MANUAL ou SS_PERFORMANCE (promove automaticamente se tiver bom desempenho). |
| first_comment | Até 2.200 caracteres. Publicado como primeiro comentário logo depois do post. |
| comments_disabled | true desativa os comentários do post depois da publicação. Padrão false. |
Música só em contas instagram_business
A biblioteca de músicas do Instagram só está disponível para contas conectadas como instagram_business. Em contas instagram, o Instagram publica o reels sem a música e não devolve erro.
Marcação de pessoas (user_tags)
A marcação é feita por mídia: em carrossel e story, cada arquivo tem as suas próprias tags.
- Imagem: x e y definem a posição da marcação, de 0 a 1 a partir do canto superior esquerdo. Informe os dois ou nenhum; sem posição, a marcação vai para o centro (0.5, 0.5).
- Vídeo (reels e vídeos de story): só o username. Enviar x ou y é recusado. Em vídeos de carrossel, as tags são ignoradas.
- Carrossel em conta instagram (login do Instagram): tags não são aceitas e a requisição é recusada com 422 e error_code carousel_tags_not_supported. Em instagram_business, as tags vão nas imagens.
- O username é normalizado: o @ inicial é removido e tudo vira minúsculo. @Maria.Silva é gravado como maria.silva. O mesmo vale para collaborators.
- Usernames aceitam apenas letras, números, ponto e underline, até 30 caracteres.
Convertendo uma grade de posições
Se a sua interface posiciona a marcação numa grade, envie o centro da célula: x = (coluna + 0,5) / colunas e y = (linha + 0,5) / linhas, com coluna e linha começando em 0. Numa grade de 3 colunas por 9 linhas, a célula da primeira coluna na terceira linha vira x 0.1667 e y 0.2778.
Erros do Instagram
Além dos códigos gerais, a publicação no Instagram pode falhar com os códigos abaixo em publishing.error.code. Os numéricos repetem o código de erro da Graph API (code ou code/subcode), e message repete a mensagem original, em inglês. Os mais comuns:
| error.code | Descrição |
|---|---|
| ERROR | O Instagram recusou a mídia ao processá-la (formato, duração, proporção). message traz o motivo informado pelo Instagram. |
| EXPIRED | O container de mídia expirou antes de ser publicado. |
| container_missing | Não havia container para publicar. Envie de novo com uma Idempotency-Key nova. |
| 190 | Token de acesso inválido ou expirado. A integração é marcada como expirada. |
| 36000 | Imagem maior que o tamanho permitido. |
| 36001 | Proporção da mídia não suportada. |
| 36004 | Legenda maior que o permitido. |
| 9004 | Mais hashtags que o permitido. |
| 9004/2207052 | O Instagram não conseguiu baixar a mídia: confira se a URL é pública e continua válida. |
| 10/2207081 | A conta não pode publicar trial reels. |
| 110/2207018 | Não é possível marcar ou acessar um usuário privado. |
| 9007 | A mídia já foi publicada. |
Token expirado
Um erro de token (190) também marca a integração do customer como expirada, pelo mesmo fluxo do restante da API, e os webhooks de integração são disparados normalmente. Novos posts para essa conta são recusados com integration_inactive até o customer reconectar.
Exemplos por Formato
Corpos completos de POST /posts para cada formato do Instagram. O exemplo de feed com uma imagem está em Criar post no Instagram.
Feed com 3 mídias: imagens e vídeo misturados, marcação na primeira imagem, colaborador e primeiro comentário.
{
"external_reference": "carousel-001",
"format": "feed",
"caption": "Bastidores da sessão de fotos",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/1.jpg",
"alt_text": "Equipe no estúdio",
"user_tags": [{ "username": "joao_fotografo", "x": 0.5, "y": 0.8333 }]
},
{ "type": "video", "url": "https://cdn.example.com/posts/2.mp4" },
{ "type": "image", "url": "https://cdn.example.com/posts/3.jpg" }
],
"instagram": {
"collaborators": ["joao_fotografo"],
"first_comment": "Qual foto você prefere?"
}
}Publique em Páginas do Facebook conectadas pelo customer, com integration_slug facebook. Perfis pessoais e grupos não são suportados.
Formatos suportados
- feed: post de texto, de link ou com 1 a 10 imagens.
- reels: exatamente um vídeo, com título opcional.
- story: 1 a 10 mídias, cada uma publicada como um story separado, na ordem enviada.
Criar post no Facebook
Grava o post e coloca a publicação na fila. A resposta sai em seguida com status 202; o resultado da publicação é consultado depois.
/postsCriar post no Facebook
Valida o pedido, grava o post e coloca a publicação na fila. Responde 202 com o post em pending, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "fb-post-2026-10-09-001",
"format": "feed",
"caption": "Coleção de verão chegando às lojas",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/colecao.jpg",
"alt_text": "Modelo usando a coleção de verão"
}
]
}Corpo da requisição
| Campo | Descrição |
|---|---|
| customer_integration | Na query, não no corpo. uuid da conexão da Página, de GET /customer-integrations. Com ele, não envie integration_slug nem source_name. |
| integration_slug | Obrigatório sem customer_integration. facebook. |
| source_name | Obrigatório sem customer_integration. Nome da Página de destino, como aparece na integração do customer. A Página precisa estar conectada e ativa. |
| external_reference | Obrigatório, até 100 caracteres. O identificador do post no seu sistema. Impede publicar o mesmo post duas vezes na mesma Página e permite listar as tentativas com GET /posts. |
| format | Obrigatório. feed, reels ou story. |
| caption | Opcional, até 2.200 caracteres. No feed é o texto do post; no reels, a descrição do vídeo. Obrigatória no feed sem mídia e sem link. Não é aceita em story. |
| media | Lista de até 10 mídias, na ordem de publicação. Obrigatória em reels e story; opcional no feed. |
| media.*.type | Obrigatório. image ou video. |
| media.*.url | Obrigatório. URL https pública, até 2.048 caracteres, de onde o Facebook baixa o arquivo. URLs assinadas precisam continuar válidas até a publicação terminar. Sem onde hospedar o arquivo? Envie pela seção Mídia (POST /media) e use a url que ela devolve. |
| media.*.alt_text | Opcional, até 1.000 caracteres. Texto alternativo das imagens, enviado no feed e nos stories. |
| Opcional. Opções específicas do Facebook. Cada formato aceita um conjunto de campos; qualquer outro é recusado com 422. |
Regras por formato
| format | media | caption | |
|---|---|---|---|
| feed | Nenhuma (post de texto ou de link), ou de 1 a 10 imagens | Sim. Obrigatória sem mídia e sem link | link |
| reels | Exatamente 1 vídeo | Sim, vira a descrição do vídeo | title |
| story | 1 a 10 mídias, imagem ou vídeo | Não |
Vídeo no feed é recusado: no Facebook, vídeo de Página é publicado como reel, então use format reels. Um post com link não leva mídia, porque o Facebook descarta as imagens de um post com link.
Opções do Facebook
| Campo | Descrição |
|---|---|
| link | Feed: URL http ou https, até 2.048 caracteres, publicada com prévia do link. Não pode ser combinada com media. |
| title | Reels: título do vídeo, até 255 caracteres. |
O que o Facebook não aceita
Posts do Facebook não aceitam user_tags nem o bloco instagram: os dois são recusados com 422. Também não há primeiro comentário, colaboradores, localização nem desativação de comentários.
Erros do Facebook
Além dos códigos gerais, a publicação no Facebook pode falhar com os códigos abaixo em publishing.error.code. Os numéricos repetem o código de erro da Graph API (code ou code/subcode), e message repete a mensagem original, em inglês. Limites de taxa (4, 17, 32 e 341) e instabilidades (1 e 2) são tentados de novo automaticamente; se persistirem, o post falha com o próprio código. Os mais comuns:
| error.code | Descrição |
|---|---|
| container_error | O Facebook recusou o vídeo ao recebê-lo ou processá-lo (formato, duração, arquivo corrompido). message traz o motivo informado pelo Facebook. |
| container_missing | Não havia post preparado para publicar. Envie de novo com uma Idempotency-Key nova. |
| 190 | Token de acesso inválido ou expirado, com ou sem subcode (190/463, por exemplo). A integração é marcada como expirada. A exceção é 190/459: a conta está bloqueada para verificação de segurança, e o dono precisa entrar no Facebook para liberá-la. |
| 10 | Sem permissão para publicar na Página. Peça ao customer para reconectar a conta concedendo todas as permissões. |
| 10/4854002 | O Facebook exige que o administrador da Página confirme a identidade antes de publicar. O mesmo vale para 368/4854002. |
| 200 | Permissão insuficiente: o usuário que conectou a Página não tem a função necessária para publicar nela. |
| 100 | O Facebook recusou algum dado do post, como o link ou a URL da mídia. message traz o detalhe. |
| 368 | O conteúdo foi bloqueado pelas políticas do Facebook, ou a Página está temporariamente impedida de publicar. |
| 368/1404112 | A Página está com acesso limitado pelo Facebook. |
| 506 | Post duplicado: o Facebook recusou um conteúdo igual a um publicado recentemente na Página. |
| 1609005 | Formato de vídeo não suportado. |
| 1609006 | Vídeo maior que o permitido. O Facebook também pode responder 350 para o mesmo caso. |
| 352 | O Facebook não aceitou o arquivo de vídeo enviado. |
Token expirado
Um erro de token (190 ou 102) também marca a integração do customer como expirada, pelo mesmo fluxo do restante da API, e os webhooks de integração são disparados normalmente. Novos posts para essa Página são recusados com integration_inactive até o customer reconectar.
Exemplos por Formato
Corpos completos de POST /posts para cada tipo de post no Facebook. O exemplo de feed com uma imagem está em Criar post no Facebook.
Post só com texto. Sem media e sem link, a caption é obrigatória.
{
"external_reference": "fb-text-001",
"format": "feed",
"caption": "Amanhã a loja abre às 10h. Te esperamos!"
}Threads
Publique em contas do Threads conectadas pelo customer, com integration_slug threads. O Threads só tem feed.
Formatos suportados
- texto: só a caption, com link_attachment opcional para mostrar a prévia de um link.
- mídia: 1 imagem ou 1 vídeo, com caption opcional.
- carrossel: de 2 a 20 mídias, imagens e vídeos misturados.
/postsCriar post no Threads
Valida o pedido, grava o post e coloca a publicação na fila. Responde 202 com o post em created, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "threads-001",
"format": "feed",
"caption": "Qual peça da coleção vocês querem ver primeiro? #verao",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/colecao-1.jpg",
"alt_text": "Vestido da coleção de verão"
},
{
"type": "image",
"url": "https://cdn.example.com/posts/colecao-2.jpg"
}
],
"threads": {
"reply_control": "accounts_you_follow",
"topic_tag": "Moda",
"first_comment": "Link da loja na bio"
}
}Regras por formato
| format | media | caption | threads |
|---|---|---|---|
| feed | Nenhuma (texto), 1 mídia, ou de 2 a 20 mídias em carrossel | Até 500 caracteres. Obrigatória num post sem mídia | reply_control, link_attachment, topic_tag, first_comment |
Um post com link_attachment não leva mídia. user_tags não é aceito no Threads.
Opções do Threads
| Campo | Descrição |
|---|---|
| reply_control | Quem pode responder: everyone, accounts_you_follow, mentioned_only, parent_post_author_only ou followers_only. |
| link_attachment | URL mostrada com prévia de link. Só em post de texto, sem mídia. |
| topic_tag | Tópico do post, até 50 caracteres, sem ponto e sem &. |
| first_comment | Resposta publicada logo depois do post, até 500 caracteres. Se ela falhar, o post continua publicado. |
Erros do Threads
Os numéricos repetem o código de erro da API do Threads (code ou code/subcode), e message repete a mensagem original, em inglês. Instabilidade (2) e limite de taxa (4) são tentados de novo automaticamente antes de o post falhar. Os mais comuns:
| error.code | Descrição |
|---|---|
| 190 | Token de acesso inválido ou expirado. A integração é marcada como expirada. |
| 10 | A conta não tem permissão para publicar. O mesmo vale para 200 e 403. |
| 100 | Parâmetro inválido. message traz o campo que o Threads recusou. |
| 368 | O Threads bloqueou o conteúdo por violar as regras da rede. |
| 36000 | Mídia maior que o tamanho permitido. |
| 36001 | Proporção da mídia não suportada. |
| 36001/2207084 | Imagem em WebP, formato que o Threads não aceita. |
| 36004 | Texto maior que o permitido. |
Exemplos por Formato
Post só de texto com prévia de link, aberto a respostas de todos. Sem mídia.
{
"external_reference": "threads-text-001",
"format": "feed",
"caption": "Saiu o guia de tendências do verão",
"threads": {
"link_attachment": "https://www.example.com/blog/tendencias-verao",
"reply_control": "everyone"
}
}Publique em Páginas de empresa (integration_slug linkedin) ou em perfis pessoais (linkedin_personal). O LinkedIn só tem feed.
Formatos suportados
- texto: só a caption.
- imagens: de 1 a 20 imagens num post só.
- vídeo: exatamente 1 vídeo, com título opcional em linkedin.title.
- documento: exatamente 1 documento (PDF, PPT ou DOC), com título obrigatório em linkedin.title.
/postsCriar post no LinkedIn
Valida o pedido, grava o post e coloca a publicação na fila. Responde 202 com o post em created, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "linkedin-001",
"format": "feed",
"caption": "Bastidores do nosso evento de lançamento",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/evento-1.jpg",
"alt_text": "Palco do evento"
},
{
"type": "image",
"url": "https://cdn.example.com/posts/evento-2.jpg"
}
]
}Regras por formato
| format | media | caption | |
|---|---|---|---|
| feed | Nenhuma (texto), de 1 a 20 imagens, 1 vídeo ou 1 documento | Até 3.000 caracteres. Obrigatória num post sem mídia | title |
Vídeo e documento vão sozinhos, sem outras mídias. media.*.type document só é aceito no LinkedIn. user_tags não é aceito.
Opções do LinkedIn
| Campo | Descrição |
|---|---|
| title | Título do vídeo ou do documento, até 255 caracteres. Obrigatório em documento. Não é aceito em post de texto ou de imagens. |
Erros do LinkedIn
Fora os códigos abaixo, error.code traz o code que o LinkedIn devolveu, e message repete a mensagem original, em inglês. Um 401 do LinkedIn é erro de token: a integração é marcada como expirada. Limite de taxa e instabilidade (409, 429 e 5xx) são tentados de novo automaticamente.
| error.code | Descrição |
|---|---|
| PROCESSING_FAILED | O LinkedIn não conseguiu processar a mídia. message traz o motivo informado pelo LinkedIn. |
| upload_failed | O arquivo não chegou ao LinkedIn depois de várias tentativas de envio. |
| media_size_unknown | A URL da mídia não informou o tamanho do arquivo (Content-Length), que o envio de vídeo exige. Sirva o arquivo com Content-Length. |
| media_unreachable | A URL da mídia não respondeu. É tentado de novo; o post falha se a URL continuar fora do ar. |
Exemplos por Formato
Post só com texto.
{
"external_reference": "linkedin-text-001",
"format": "feed",
"caption": "Estamos contratando! Vagas abertas em tecnologia."
}Publique pins em contas do Pinterest conectadas pelo customer, com integration_slug pinterest. Todo pin vai para um board.
Formatos suportados
- imagem: 1 imagem, com título, link e alt_text (até 500 caracteres).
- vídeo: 1 vídeo, com capa obrigatória em pinterest.cover_url.
/postsCriar pin no Pinterest
Valida o pedido, grava o pin e coloca a publicação na fila. Responde 202 com o post em created, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "pin-001",
"format": "feed",
"caption": "Paleta de cores da coleção de verão",
"media": [
{
"type": "image",
"url": "https://cdn.example.com/posts/paleta.jpg",
"alt_text": "Tecidos em tons de areia"
}
],
"pinterest": {
"board_id": "1094937759896573345",
"title": "Coleção de verão",
"link": "https://www.example.com/colecao-verao"
}
}Regras por formato
| format | media | caption | |
|---|---|---|---|
| feed | Exatamente 1 imagem ou 1 vídeo | Até 800 caracteres (a descrição do pin) | board_id, board_section_id, title, link, cover_url |
pinterest.board_id é obrigatório. Os ids de board e de seção vêm das rotas de boards abaixo. user_tags não é aceito.
Opções do Pinterest
| Campo | Descrição |
|---|---|
| board_id | Obrigatório. id numérico do board de destino, de GET /posts/pinterest/boards. |
| board_section_id | Opcional. id numérico da seção dentro do board, de GET /posts/pinterest/boards/{board_id}/sections. |
| title | Título do pin, até 100 caracteres. |
| link | URL de destino do pin. |
| cover_url | Capa do pin de vídeo, https. Obrigatória em vídeo e recusada em imagem. |
Boards e seções
Leituras e criação ao vivo na conta do Pinterest, sem nada guardado no Connect. Recebem a conta pela conexão (customer_integration) ou pelo nome (source_name), na query ou, ao criar um board, no corpo.
/posts/pinterest/boardsListar boards
Lista os boards da conta do Pinterest, lidos ao vivo na rede. Use o id em pinterest.board_id.
{
"boards": [
{
"id": "1094937759896573345",
"name": "Coleção de verão",
"description": "Peças e paletas da estação",
"privacy": "PUBLIC"
},
{
"id": "1094937759896573400",
"name": "Rascunhos",
"description": null,
"privacy": "SECRET"
}
]
}/posts/pinterest/boardsCriar board
Cria um board na conta do Pinterest e devolve o board criado com 201. name é obrigatório (até 180 caracteres); description (até 500) e privacy (PUBLIC, PROTECTED ou SECRET) são opcionais. Nada é guardado no Connect.
{
"name": "Coleção de inverno",
"description": "Peças da próxima estação",
"privacy": "PUBLIC"
}/posts/pinterest/boards/{board_id}/sectionsListar seções do board
Lista as seções de um board, lidas ao vivo na rede. Use o id em pinterest.board_section_id. Board que não existe na conta responde 404 board_not_found.
{
"sections": [
{
"id": "5170127316372340412",
"name": "Looks"
}
]
}Erros do Pinterest
Fora os códigos abaixo, error.code traz o code que o Pinterest devolveu, e message repete a mensagem original, em inglês. Limite de taxa e instabilidade (429 e 5xx) são tentados de novo automaticamente.
| error.code | Descrição |
|---|---|
| 2 | Token de acesso inválido ou expirado. A integração é marcada como expirada. |
| board_not_found | O board ou a seção não existe na conta. |
| permission_denied | A conta não tem permissão para a operação, por exemplo num board de outro usuário. |
| media_missing | O pin não tinha mídia para publicar. |
Exemplos por Formato
Pin de vídeo numa seção do board, com a capa obrigatória.
{
"external_reference": "pin-video-001",
"format": "feed",
"caption": "Como montar um look de verão",
"media": [
{
"type": "video",
"url": "https://cdn.example.com/posts/look.mp4"
}
],
"pinterest": {
"board_id": "1094937759896573345",
"board_section_id": "5170127316372340412",
"title": "Look de verão",
"cover_url": "https://cdn.example.com/posts/look-capa.jpg"
}
}YouTube
Publique Shorts em canais do YouTube conectados pelo customer, com integration_slug youtube.
Formatos suportados
- shorts: exatamente 1 vídeo. O YouTube decide se é Short pelo próprio vídeo (vertical, até 3 minutos); o format só declara a intenção.
/postsPublicar Short no YouTube
Valida o pedido, grava o post e coloca a publicação na fila. Responde 202 com o post em created, ou 200 com o header Idempotent-Replay: true quando a mesma requisição é reenviada.
{
"external_reference": "short-001",
"format": "shorts",
"caption": "Três jeitos de usar a mesma camisa",
"media": [
{
"type": "video",
"url": "https://cdn.example.com/posts/camisa.mp4"
}
],
"youtube": {
"title": "Três jeitos de usar a mesma camisa",
"privacy_status": "public",
"category_id": "26",
"tags": [
"moda",
"estilo",
"verão"
],
"made_for_kids": false,
"notify_subscribers": true
}
}Regras por formato
| format | media | caption | youtube |
|---|---|---|---|
| shorts | Exatamente 1 vídeo, sem alt_text | A descrição do vídeo, até 5.000 bytes, sem < e > | title, privacy_status, category_id, tags, made_for_kids, notify_subscribers |
youtube.title e youtube.privacy_status são obrigatórios. user_tags não é aceito.
Opções do YouTube
| Campo | Descrição |
|---|---|
| title | Obrigatório. Título do vídeo, até 100 caracteres, sem < e >. |
| privacy_status | Obrigatório. public, unlisted ou private. |
| category_id | id numérico da categoria, de GET /posts/youtube/categories. |
| tags | Lista de tags, sem <, > e vírgulas. Juntas, até 500 caracteres contados como o YouTube mostra: separadas por vírgula, e uma tag com espaço entre aspas. |
| made_for_kids | Se o vídeo é feito para crianças. |
| notify_subscribers | Se os inscritos são avisados da publicação. |
Categorias
Leitura ao vivo das categorias do YouTube, cacheada por região e idioma. Recebe a conta pela conexão (customer_integration) ou pelo nome (source_name), na query.
/posts/youtube/categoriesListar categorias
Lista as categorias que um vídeo pode receber na região de region_code (padrão BR), com os títulos no idioma de hl. Use o id em youtube.category_id.
{
"categories": [
{
"id": "22",
"title": "Pessoas e blogs"
},
{
"id": "26",
"title": "Guias e estilo"
}
]
}Erros do YouTube
error.code traz o reason que o Google devolveu, e message repete a mensagem original, em inglês. Um 401 é erro de token: a integração é marcada como expirada. rateLimitExceeded, userRateLimitExceeded e backendError são tentados de novo automaticamente. Os mais comuns:
| error.code | Descrição |
|---|---|
| quotaExceeded | A cota diária da API do YouTube acabou. Ela renova à meia-noite do horário do Pacífico: envie de novo depois, com outra Idempotency-Key. |
| uploadLimitExceeded | O canal atingiu o limite de envios do YouTube. |
| forbidden | A conta não tem permissão para enviar vídeos ao canal. |
Exemplos por Formato
O mínimo para publicar: o vídeo, o título e a privacidade. Sem descrição.
{
"external_reference": "short-unlisted-001",
"format": "shorts",
"media": [
{
"type": "video",
"url": "https://cdn.example.com/posts/previa.mp4"
}
],
"youtube": {
"title": "Prévia da coleção",
"privacy_status": "unlisted"
}
}Consultar Posts
Consulte um post pelo uuid ou liste os posts do customer, com filtros opcionais por conexão (customer_integration), external_reference e status. As duas rotas só enxergam posts do customer do x-customer-token; sem o header, respondem 401. Os posts de todos os clientes ficam em GET /merchants/posts, na seção Merchants.
/posts/{uuid}Consultar post
Retorna o post com o estado atual da publicação. Use para acompanhar o post até um status final.
{
"post": {
"uuid": "9f1c2b7e-1d3a-4c55-9a8e-2f6b0c1d4e5f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"external_reference": "post-2026-10-08-001",
"integration_slug": "instagram_business",
"source_id": "17841400000000000",
"source_name": "minha_marca",
"format": "feed",
"caption": "Coleção de verão com @parceiro_oficial #verao",
"status": "published",
"permalink": "https://www.instagram.com/p/DePI16BER0c/",
"publishing": {
"status": "published",
"external_id": "18393642439204130",
"error": null,
"published_at": "2026-10-08 14:00:42"
},
"media": [
{
"position": 0,
"type": "image",
"url": "https://cdn.example.com/posts/colecao.jpg",
"alt_text": "Modelo usando a coleção de verão",
"user_tags": [
{ "username": "maria.silva", "x": 0.1667, "y": 0.2778 },
{ "username": "loja.centro" }
],
"publishing": { "status": "published", "external_id": null, "error": null }
}
],
"instagram": {
"collaborators": ["parceiro_oficial"],
"location_id": "106377336067638",
"first_comment": "Link na bio!",
"comments_disabled": false
},
"created_at": "2026-10-08 14:00:00"
}
}/postsListar posts
Lista os posts do customer, do mais recente para o mais antigo, incluindo as tentativas que falharam. Filtros opcionais: customer_integration, external_reference e status (created, publishing, published ou failed).
| Parâmetros | Descrição |
|---|---|
| customer_integration | O uuid de uma conexão do customer, para ficar só com os posts da conta dela. Uma conexão de outro customer responde 403. |
| status | created, publishing, published ou failed. |
| external_reference | Os posts com essa referência: as tentativas que falharam e o mesmo conteúdo publicado em contas diferentes. |
| per_page | De 1 a 100, padrão 15. |
{
"data": [
{
"uuid": "9f1c2b7e-1d3a-4c55-9a8e-2f6b0c1d4e5f",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"external_reference": "post-2026-10-08-001",
"status": "published",
"publishing": { "status": "published", "external_id": "18393642439204130", "error": null, "published_at": "2026-10-08 14:00:42" },
"...": "..."
},
{
"uuid": "1b2e7c90-55aa-4f0e-8d0c-3a9e6f7d2c11",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"external_reference": "post-2026-10-08-001",
"status": "failed",
"publishing": {
"status": "failed",
"external_id": null,
"error": { "code": "36001", "message": "The aspect ratio is not supported." },
"published_at": null
},
"...": "..."
}
],
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://connect.reportei.com/api/posts",
"per_page": 15,
"to": 2,
"total": 2,
"sortBy": null,
"descending": false
}
}Campos da resposta
| Campo | Descrição |
|---|---|
| uuid | Identificador do post no Connect. Use nas consultas. |
| customer_uuid | O uuid do customer dono do post. |
| external_reference | A referência que você enviou. |
| source_id | ID da conta de destino na rede, copiado da integração. |
| status | Status geral do post: created, publishing, published ou failed. Veja Status da Publicação. |
| permalink | Link público do post na rede, preenchido depois da publicação. Sempre null em story. |
| publishing | Estado detalhado da publicação: status, external_id (ID do post publicado na rede), error e published_at. |
| publishing.error | null, ou { code, message } quando a publicação falhou. Veja Erros. |
| media | As mídias na ordem de publicação, com position, type, url, alt_text e as user_tags já normalizadas. |
| media.*.publishing | Estado da publicação de cada mídia (status, external_id e error). Em story, cada mídia é um story com o seu próprio external_id. |
| As opções do Instagram enviadas, só com os campos preenchidos. null quando nenhuma foi enviada. | |
| As opções do Facebook enviadas (link e title), só com os campos preenchidos. null quando nenhuma foi enviada. | |
| threads | As opções do Threads enviadas, só com os campos preenchidos. null quando nenhuma foi enviada. |
| As opções do LinkedIn enviadas (title). null quando nenhuma foi enviada. | |
| As opções do Pinterest enviadas, só com os campos preenchidos. null quando nenhuma foi enviada. | |
| youtube | As opções do YouTube enviadas, só com os campos preenchidos. null quando nenhuma foi enviada. |
A listagem é paginada (per_page de 1 a 100, padrão 15) e traz os posts mais recentes primeiro. Uma external_reference pode ter vários posts: tentativas que falharam e o mesmo conteúdo publicado em contas diferentes.
Status da Publicação
O post tem dois status: post.status é o resumo para a sua interface, e publishing.status é o detalhe do processo.
post.status
| Status | Descrição |
|---|---|
| created | Recebido, aguardando o início da publicação. |
| publishing | Publicação em andamento. |
| published | Publicado. publishing.status pode ser published ou partially_published. |
| failed | Nada foi publicado. O motivo está em publishing.error. |
publishing.status
| Status | Descrição |
|---|---|
| pending | Na fila, aguardando o processamento. |
| processing | Mídias enviadas; a rede está processando. |
| ready | Só em mídias: a mídia foi processada e está pronta para compor o carrossel ou ser publicada no story. |
| publishing | Publicação sendo enviada à rede. |
| published | Publicado. Status final. |
| partially_published | Só em story: parte dos stories foi publicada e parte falhou. O que já foi ao ar não é desfeito. Status final. |
| failed | Falhou. Status final. |
Acompanhando a publicação
Consulte GET /posts/{uuid} a cada 15 a 30 segundos até um status final (published, partially_published ou failed). Imagens costumam ser publicadas em segundos; vídeos podem levar alguns minutos. Se a rede não terminar de processar a mídia em 1 hora, a publicação falha com publishing_timeout. Falhas temporárias (limite de taxa, instabilidade) são tentadas de novo automaticamente antes de o post falhar.
Receber o resultado por webhook
Em vez de consultar, assine post.published, post.partially_published e post.failed em /webhooks/endpoints (veja Webhooks na referência da API). Cada evento chega com data.object igual ao GET /posts/{uuid}, já no status final.
Primeiro comentário e comentários desativados
first_comment e comments_disabled são aplicados depois que o post vai ao ar. Se uma dessas ações falhar, o post continua published: a publicação não é desfeita por causa delas.
Erros
Os erros aparecem em dois momentos: na requisição, quando o Connect recusa o pedido antes de gravar o post, e na publicação, quando o post foi aceito mas não chegou à rede. Os códigos específicos de cada rede estão na seção da rede.
Erros na requisição
Respondidos na hora. Recusas de negócio trazem message e error_code; erros de validação trazem errors, com as mensagens por campo.
| Status | error_code | Descrição |
|---|---|---|
| 401 | Bearer token ausente ou inválido, x-customer-token ausente ou inválido, ou customer com o teste encerrado e sem pagamento. | |
| 403 | O customer_integration informado não é deste customer. | |
| 404 | GET /posts/{uuid} de um post que não existe ou que é de outro customer. | |
| 422 | errors | Corpo inválido ou Idempotency-Key ausente. errors traz as mensagens por campo. |
| 422 | idempotency_key_reused | A Idempotency-Key já foi usada com outro corpo. |
| 422 | integration_not_found | O customer não tem integração com esse integration_slug e source_name. |
| 422 | integration_ambiguous | Mais de uma integração ativa do customer tem esse source_name. Envie a conta pela conexão, com customer_integration. |
| 422 | integration_inactive | A integração existe, mas não está ativa (por exemplo, token expirado). Peça ao customer para reconectar a conta. |
| 422 | carousel_tags_not_supported | Carrossel com user_tags numa conta conectada pelo login do Instagram, que não aceita marcação em carrossel. |
| 409 | post_in_progress | Já existe uma publicação em andamento para a mesma conta e external_reference. Traz post_uuid. |
| 409 | post_already_published | A mesma conta e external_reference já foram publicadas. Traz post_uuid. |
{
"message": "A post with this external_reference was already published to this account.",
"error_code": "post_already_published",
"post_uuid": "9f1c2b7e-1d3a-4c55-9a8e-2f6b0c1d4e5f"
}{
"errors": {
"media.0.user_tags.0": ["Tags on a video do not take x and y."],
"instagram.first_comment": ["The first_comment option is not accepted for story."]
}
}Erros na publicação
Quando a publicação falha, post.status vira failed e publishing.error traz code e message. Em story, cada mídia tem o seu próprio error, e o post pode terminar como partially_published.
"publishing": {
"status": "failed",
"external_id": null,
"error": {
"code": "36001",
"message": "The aspect ratio is not supported."
},
"published_at": null
}Códigos gerais
| error.code | Descrição |
|---|---|
| integration_inactive | A integração deixou de estar ativa antes da publicação. |
| publishing_timeout | A rede não terminou de processar a mídia em 1 hora. |
| generator_invalid_request | O serviço de publicação recusou o pedido. Fale com o suporte informando o uuid do post. |
| unexpected_error | Erro inesperado depois de esgotadas as tentativas. Fale com o suporte informando o uuid do post. |
Toolkits
Os toolkits conectam os seus clientes a apps de terceiros que não são plataformas de métricas, como Notion, Jira e Google Calendar. Cada toolkit tem tools, as operações que você executa na conta do cliente: buscar páginas no Notion, ler ou criar uma issue no Jira, criar um evento na agenda. A resposta de cada execução é a do próprio app. Os toolkits não têm coleta de métricas nem webhooks: são ações sob demanda.
Como funciona
- Escolha o toolkit e a tool no catálogo. O input de cada tool é o JSON Schema dos argumentos, o que você entrega a um agente para ele escolher a tool e preencher a chamada.
- Conecte o cliente ao app pela sessão de integração, com toolkit_slug, ou por POST /customer-toolkits, quando você já tem a chave dele. A conexão ganha um uuid.
- Execute a tool com POST /tools/{tool_slug}/execute, passando o uuid da conexão em customer_toolkit e os argumentos em arguments.
No modo oauth2, o toolkit conecta pelo seu app OAuth, não por um app do Reportei. Cadastre o client ID e o client secret em POST /merchants/credentials/{toolkit_slug} antes de abrir a sessão. Sem esse cadastro, um toolkit que só conecta por OAuth, como o Google Calendar, recusa a sessão com 422 toolkit_credential_missing. O passo a passo está em Toolkits OAuth com o seu app.
Catálogo
O catálogo é o mesmo para todo merchant e não depende de cliente, então as rotas dele levam só o Bearer. Cada toolkit diz em auth os modos de conexão que aceita. Nos modos api_key e basic, fields traz o rótulo de cada campo e steps diz onde o cliente encontra a credencial no app, o que serve para montar o seu próprio formulário. logo_url traz o logo oficial do app, servido pelo Connect, para você mostrar no seu catálogo: um SVG, ou um PNG quando a marca só publica o ícone em PNG. Vem null quando o app não tem logo, e a sessão mostra a inicial do nome no lugar. Leia a lista em GET /toolkits em vez de fixá-la no seu código: apps novos entram no catálogo sem mudança na API.
| mode | O que o cliente informa | Como conectar |
|---|---|---|
| api_key | Uma chave de API, em key. | Pela sessão, num formulário, ou por POST /customer-toolkits. |
| basic | Usuário e senha, em username e password. No Jira, o e-mail da conta e um token de API. | Pela sessão, num formulário, ou por POST /customer-toolkits. |
| oauth2 | Nenhuma credencial: o cliente autoriza no consentimento do app. | Só pela sessão, com o seu app OAuth cadastrado para o toolkit. |
Variável do host
Alguns apps têm um endereço por conta, como o Jira Cloud em acme.atlassian.net. O toolkit declara a variável em base_url_params, como site no Jira, e o cliente informa só o subdomínio: letras minúsculas, números e hífen, até 63 caracteres, começando por letra ou número. O domínio é sempre o do app, então a credencial nunca vai para outro endereço.
/toolkitsListar toolkits
O catálogo inteiro, sem paginação. Filtre por category, como productivity, project_management ou scheduling, e por search, que procura no slug, no nome e na descrição. tools_count diz quantas tools cada toolkit tem.
{
"data": [
{
"slug": "google_calendar",
"name": "Google Calendar",
"description": "Calendars and events of a Google account.",
"category": "scheduling",
"version": "2026-10-19",
"docs_url": "https://developers.google.com/workspace/calendar/api/v3/reference",
"logo_url": "https://connect.reportei.com/img/toolkits/google_calendar.svg",
"auth": [
{
"mode": "oauth2",
"merchant_credential_required": true,
"scopes": [
"openid",
"email",
"profile",
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
"https://www.googleapis.com/auth/calendar.events"
]
}
],
"base_url_params": {},
"tools_count": 3
},
{
"slug": "jira",
"name": "Jira",
"description": "Issues and projects of a Jira Cloud site.",
"category": "project_management",
"version": "2026-10-19",
"docs_url": "https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/",
"logo_url": "https://connect.reportei.com/img/toolkits/jira.svg",
"auth": [
{
"mode": "basic",
"merchant_credential_required": false,
"fields": {
"username": {
"label": "Atlassian account email",
"placeholder": "you@company.com"
},
"password": {
"label": "API token",
"placeholder": "Paste your API token"
}
},
"steps": [
"Sign in to id.atlassian.com with the account you are connecting and open Security, then API tokens.",
"Choose Create API token, the option without scopes, and set an expiration of up to one year.",
"Paste the token below together with the email of the same account."
]
}
],
"base_url_params": {
"site": {
"label": "Site name",
"placeholder": "your-company, from your-company.atlassian.net",
"format": "subdomain"
}
},
"tools_count": 3
}
]
}/toolkits/{toolkit_slug}Consultar toolkit
Um toolkit pelo slug, com os modos de conexão, os campos que cada um pede ao cliente e as variáveis do host. Responde 404 toolkit_not_found quando nenhum toolkit habilitado tem esse slug.
{
"toolkit": {
"slug": "jira",
"name": "Jira",
"description": "Issues and projects of a Jira Cloud site.",
"category": "project_management",
"version": "2026-10-19",
"docs_url": "https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/",
"logo_url": "https://connect.reportei.com/img/toolkits/jira.svg",
"auth": [
{
"mode": "basic",
"merchant_credential_required": false,
"fields": {
"username": {
"label": "Atlassian account email",
"placeholder": "you@company.com"
},
"password": {
"label": "API token",
"placeholder": "Paste your API token"
}
},
"steps": [
"Sign in to id.atlassian.com with the account you are connecting and open Security, then API tokens.",
"Choose Create API token, the option without scopes, and set an expiration of up to one year.",
"Paste the token below together with the email of the same account."
]
}
],
"base_url_params": {
"site": {
"label": "Site name",
"placeholder": "your-company, from your-company.atlassian.net",
"format": "subdomain"
}
},
"tools_count": 3
}
}/toolkits/{toolkit_slug}/toolsListar tools
As tools de um toolkit, sem paginação. method diz se a tool lê (GET) ou escreve no app, e input é o JSON Schema dos arguments que a execução recebe.
{
"data": [
{
"slug": "jira_get_issue",
"toolkit_slug": "jira",
"name": "Get issue",
"description": "Returns one issue by id or key.",
"method": "GET",
"input": {
"type": "object",
"additionalProperties": false,
"required": [
"issueIdOrKey"
],
"properties": {
"issueIdOrKey": {
"type": "string",
"description": "Issue id or key, for example KEY-123."
},
"fields": {
"type": "string",
"description": "Comma-separated field names to return. Omit it to return all navigable fields."
}
}
}
}
]
}/tools/{tool_slug}Consultar tool
Uma tool pelo slug, que é global e já traz o prefixo do toolkit, como jira_get_issue. Vem com o toolkit a que pertence e o input.
{
"tool": {
"slug": "jira_get_issue",
"toolkit_slug": "jira",
"name": "Get issue",
"description": "Returns one issue by id or key.",
"method": "GET",
"input": {
"type": "object",
"additionalProperties": false,
"required": [
"issueIdOrKey"
],
"properties": {
"issueIdOrKey": {
"type": "string",
"description": "Issue id or key, for example KEY-123."
},
"fields": {
"type": "string",
"description": "Comma-separated field names to return. Omit it to return all navigable fields."
}
}
}
}
}Conectar o cliente
Cada conexão é uma conta do app ligada a um cliente. Ela fica em GET /customer-toolkits, separada das conexões de plataforma, e o uuid dela é o que a execução recebe em customer_toolkit. As rotas de conexão e de execução levam o Bearer e o x-customer-token do cliente. Como em Posts e Ads, um cliente com o teste encerrado e sem pagamento recebe 401 customer_trial_ended nelas e na sessão, até ser marcado como pagante.
Pela sessão de integração
Abra a sessão com toolkit_slug, que não aceita source_slug junto. O cliente vê o formulário de credencial do toolkit, ou vai para o consentimento do app quando o toolkit conecta por OAuth, e informa na sessão o valor da variável do host, se houver. Num toolkit que aceita chave e OAuth, como o Notion, a sessão usa o OAuth quando o seu app está cadastrado para ele, e o formulário de chave quando não está.
{
"toolkit_slug": "jira",
"redirect_url": "https://sua-aplicacao.com/integracoes"
}Ao terminar, o cliente volta para a sua redirect_url com o uuid da conexão em customer_toolkit. Com close_on_finish, a aba mostra a conta conectada e fecha; a conexão aparece em GET /customer-toolkits.
https://sua-aplicacao.com/integracoes?customer_toolkit=7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6eStatus da conexão
Uma conexão é active ou expired. Ela vira expired quando o seu app OAuth que a autorizou é trocado ou removido, ou quando o app recusa renovar o token. Nos modos api_key e basic isso não acontece. Reconectar a mesma conta volta a conexão para active.
/customer-toolkitsConectar com chave
Para quando você já tem a credencial do cliente: a mesma conexão que a sessão faz, sem o formulário. O Connect envia a credencial ao app uma vez para identificar a conta e só então a guarda. Só nos modos api_key e basic: um toolkit que conecta só por OAuth responde 422 toolkit_requires_session. Conectar de novo a mesma conta troca a credencial guardada e volta a conexão para active.
Parâmetros
| Campo | Descrição |
|---|---|
| toolkit_slug | Obrigatório. Um toolkit habilitado com modo api_key ou basic. |
| key | A chave de API do cliente, no modo api_key. |
| username | A primeira parte da credencial no modo basic, como o e-mail da conta no Jira. |
| password | A segunda parte da credencial no modo basic, como o token de API no Jira. |
| base_url_params | O valor de cada variável do host do toolkit, por nome, como site no Jira. Só o subdomínio: acme, de acme.atlassian.net. |
{
"toolkit_slug": "jira",
"base_url_params": {
"site": "acme"
},
"username": "ana@acme.com",
"password": "YOUR_API_TOKEN"
}/customer-toolkitsListar conexões
As conexões de toolkit do cliente, sem paginação. Nenhum segredo sai daqui.
{
"data": [
{
"uuid": "7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6e",
"toolkit": {
"slug": "jira",
"name": "Jira"
},
"source_id": "acme:5b10ac8d82e05b22cc7d4ef5",
"source_name": "Ana Lima",
"status": "active",
"base_url_params": {
"site": "acme"
},
"expires_at": null,
"created_at": "2026-10-19T12:00:00.000000Z",
"updated_at": "2026-10-19T12:00:00.000000Z"
}
]
}/customer-toolkits/{uuid}Consultar conexão
Uma conexão pelo uuid, que vem de GET /customer-toolkits ou da redirect_url da sessão. A conexão de outro cliente responde 404 customer_toolkit_not_found.
{
"customer_toolkit": {
"uuid": "7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6e",
"toolkit": {
"slug": "jira",
"name": "Jira"
},
"source_id": "acme:5b10ac8d82e05b22cc7d4ef5",
"source_name": "Ana Lima",
"status": "active",
"base_url_params": {
"site": "acme"
},
"expires_at": null,
"created_at": "2026-10-19T12:00:00.000000Z",
"updated_at": "2026-10-19T12:00:00.000000Z"
}
}/customer-toolkits/{uuid}Remover conexão
Apaga a conexão e a credencial guardada com ela. O app não é avisado: a chave ou o consentimento continuam valendo lá, e o cliente os revoga no próprio app se quiser.
{
"success": true,
"message": "Record successfully removed"
}Toolkits OAuth com o seu app
No modo oauth2, o cliente autoriza pelo consentimento do app, e a conexão usa o app OAuth que você cria no app de terceiro, como um client OAuth no Google Cloud para o Google Calendar. Não há app do Reportei para toolkits: sem o seu, o modo oauth2 não conecta. O cadastro usa as mesmas rotas de Use sua aplicação, com o slug do toolkit em integration_slug.
Crie o app OAuth no painel de desenvolvedor do app de terceiro, cuja documentação está em docs_url, e registre nele a callback_url do Connect como URL de redirecionamento.
Cadastre o client ID e o client secret em POST /merchants/credentials/{toolkit_slug}. Sem scopes, o consentimento pede os scopes do toolkit, em auth[].scopes; com scopes, a sua lista substitui a do toolkit por inteiro.
Abra a sessão com toolkit_slug. A partir daí, a sessão desse toolkit conecta pelo seu app.
A callback_url do toolkit
Troque {toolkit_slug} pelo slug do toolkit. O endereço também volta em callback_url na resposta do cadastro. Se preferir receber o callback no seu domínio, informe redirect_url no cadastro e redirecione de lá para este endereço, com a query intacta.
https://connect.reportei.com/api/oauth/{toolkit_slug}/callbackO cadastro do app
O mesmo POST de Use sua aplicação, com o slug do toolkit no caminho. A resposta traz a callback_url e active_connections, que conta as conexões ativas feitas com o seu app.
curl -s -X POST https://connect.reportei.com/api/merchants/credentials/google_calendar \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_id": "1234567890-abc123def456.apps.googleusercontent.com",
"client_secret": "YOUR_CLIENT_SECRET"
}'Importante
Um toolkit que só conecta por OAuth, como o Google Calendar, recusa a sessão com 422 toolkit_credential_missing enquanto o seu app não estiver cadastrado para ele.
PUT com outro client_id, e DELETE, marcam como expired as conexões ativas feitas com o app anterior. O total vem em invalidated_connections, e cada cliente reconecta pela sessão. Um client_secret novo do mesmo app não invalida nada.
Renovação do token
Quando o app informa a validade do token, em expires_at, e permite renová-lo, o Connect o renova antes da execução que cair a até um minuto do vencimento. Se o app recusa a renovação, a conexão vira expired e a execução responde 409 customer_toolkit_expired; se a renovação falha por outro motivo, a execução responde 502 ou 504, sem execution_id, e a conexão segue active. Execuções simultâneas na mesma conexão esperam a renovação em curso e usam o token novo; se a espera passa de 30 segundos, a execução responde 409 customer_toolkit_refreshing: repita em seguida.
Executar uma tool
A execução chama o app na conta do cliente, com a credencial da conexão. arguments vai ao app como veio: monte-o pelo input da tool, porque o que o app aceita é decisão dele. O Connect só recusa, antes de chamar o app, um argumento que não serve para montar a URL.
Sem retry e sem idempotência
O Connect chama o app uma vez e não repete. Um POST repetido é um POST a mais no app, e um 504 não garante que a chamada não chegou: não repita às cegas uma tool que escreve. O method da tool diz se ela lê (GET) ou escreve.
O que a execução responde
Decida pelo corpo, não só pelo status: error_code é uma recusa do Connect, e api_error é um erro do app.
| Status | Descrição |
|---|---|
| 2xx | O status e o corpo do app, no Content-Type dele, sem transformação. |
| 3xx, 4xx, 5xx | Um erro do app, com o status dele, no envelope { error, api_error }: o corpo original em api_error.body e, em api_error.headers, o retry-after, os x-ratelimit-*, o x-request-id e o location de um redirect, quando o app os mandou. |
| 404, 409, 422 | Uma recusa do Connect, antes de chamar o app, com message e error_code. Os códigos estão em Erros desta superfície. |
| 502, 504 | O app não respondeu a tempo, ou a chamada falhou sem uma resposta dele, com message e error_code. Traz execution_id quando a chamada chegou a ser feita. |
A execução fica registrada quando chega à etapa de chamar o app. Se o app responde, o uuid do registro vem no header X-Toolkit-Execution-Id; no 422 tool_arguments_invalid e nos 502 e 504 dessa etapa, ele vem em execution_id, no corpo. As recusas anteriores e as falhas na renovação do token não deixam registro. Toda resposta que vem do app, de sucesso ou de erro, traz também o header X-Content-Type-Options: nosniff, porque o corpo é do app e o navegador não deve adivinhar outro tipo.
/tools/{tool_slug}/executeExecutar tool
Chama a tool na conexão do cliente e devolve a resposta do app como veio. O curl do exemplo usa -i para mostrar o header X-Toolkit-Execution-Id.
Parâmetros
| Campo | Descrição |
|---|---|
| customer_toolkit | Obrigatório. O uuid da conexão, que precisa ser do toolkit da tool e estar active. |
| arguments | Os argumentos da tool, num objeto de até 64 KB no formato do input dela. Vai ao app como veio. Pode faltar quando a tool não tem argumento obrigatório. |
Erro do app
Um 404 do Jira, como chega: o status é o do app, e o corpo dele vem em api_error.body.
{
"error": "Request failed with status code 404",
"api_error": {
"status_code": 404,
"headers": {
"x-request-id": "6f1c0e2a"
},
"body": {
"errorMessages": [
"Issue does not exist or you do not have permission to see it."
],
"errors": {}
}
}
}{
"customer_toolkit": "7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6e",
"arguments": {
"issueIdOrKey": "KEY-123",
"fields": "summary,status"
}
}/tools/executions/{uuid}Consultar execução
O registro de uma execução do cliente, pelo X-Toolkit-Execution-Id: o que foi pedido ao app, a URL alcançada sem a query string, o status e a resposta do app e quanto demorou. A resposta não traz os headers enviados. A execução de outro cliente responde 404 toolkit_execution_not_found.
{
"execution": {
"uuid": "0d3c8b4a-2e1f-4c6d-9a7b-5e4f3a2b1c0d",
"tool_slug": "jira_get_issue",
"toolkit_slug": "jira",
"customer_toolkit": "7c2a1d3e-5f6b-4a8c-9d0e-1f2a3b4c5d6e",
"customer_uuid": "0b6e1c2a-3f4d-4e5f-8a9b-1c2d3e4f5a6b",
"method": "GET",
"url": "https://acme.atlassian.net/rest/api/3/issue/KEY-123",
"payload": {
"issueIdOrKey": "KEY-123",
"fields": "summary,status"
},
"http_status": 200,
"response": {
"id": "10001",
"key": "KEY-123",
"fields": {
"summary": "Fix the login",
"status": {
"name": "In Progress"
}
}
},
"error_message": null,
"duration_ms": 412,
"created_at": "2026-10-19T12:00:00.000000Z"
}
}Execuções de todos os clientes
Para acompanhar as execuções de todos os seus clientes, só com o Bearer, use a rota de panorama na seção Merchants:
Limitações
- O texto que vem da spec do toolkit, como as descrições, os rótulos e os passos do formulário, é só em inglês. As frases do próprio Connect na sessão, como botões e erros, seguem o locale.
- Um erro do app numa execução não muda a conexão: um 401 do app volta como veio, e a conexão segue active. Reconectar é decisão sua.
- A sessão de toolkit não dispara os webhooks integration_session.* nem customer_integration.*. Acompanhe pelo customer_toolkit da redirect_url ou por GET /customer-toolkits.
- O Connect não segue redirect: um 3xx volta como o app respondeu, com o location, e a credencial nunca vai para outro endereço.
- A credencial da conexão nunca aparece numa resposta: se o app a ecoar, ela volta como [redacted], inclusive codificada (o par usuário:senha do modo basic em base64, url-encoded ou com as barras escapadas em JSON). No modo basic, o usuário também é tratado como segredo, então um app que devolve o e-mail da própria conta, como o Jira, mostra [redacted] no lugar dele.
- O registro de GET /tools/executions/{uuid} guarda payload e response decodificados: um {} aninhado aparece como [], e uma resposta acima de 64 KB, ou que não é JSON, vira { truncated, body }, com o texto cortado. O corpo que a execução devolve é o original.
Erros desta superfície
Além dos erros comuns da API, estas são as recusas do Connect nas rotas de toolkits, com message e error_code. Falha de validação do corpo ou da query volta 422 com errors.
| Status | error_code | Descrição |
|---|---|---|
| 404 | toolkit_not_found | Nenhum toolkit habilitado tem esse slug. |
| 404 | tool_not_found | Nenhum toolkit habilitado tem uma tool com esse slug. |
| 404 | customer_toolkit_not_found | A conexão não existe ou não é deste cliente. A API não distingue os dois casos. |
| 404 | toolkit_execution_not_found | A execução não existe ou não é deste cliente. |
| 409 | customer_toolkit_expired | A conexão expirou: o seu app OAuth que a autorizou foi trocado ou removido, ou o app recusou renovar o token. Reconecte pela sessão. |
| 409 | customer_toolkit_refreshing | Outra chamada está renovando o token desta conexão, e a espera passou de 30 segundos. Repita em seguida. |
| 422 | toolkit_credential_missing | Sessão de um toolkit que só conecta por OAuth, sem o seu app cadastrado para ele. |
| 422 | toolkit_requires_session | POST /customer-toolkits com um toolkit que só conecta por OAuth. Use a sessão. |
| 422 | toolkit_credential_invalid | O app recusou a credencial informada, ou não identificou a conta. |
| 422 | toolkit_host_param_invalid | O valor de uma variável do host não é um subdomínio válido. |
| 422 | tool_toolkit_mismatch | A tool não é do toolkit desta conexão. |
| 422 | tool_arguments_invalid | Um argumento que monta a URL está ausente ou vazio, ou é "." ou "..", ou um argumento de query não é um valor simples nem uma lista deles. Traz execution_id. |
| 502 | toolkit_integration_error | A chamada falhou sem uma resposta do app, ou a renovação do token falhou sem uma recusa dele. Na conexão sem tela, também quando o app responde com erro ao identificar a conta. Na execução, traz execution_id quando a chamada chegou a ser feita. |
| 504 | toolkit_timeout | O app não respondeu em 25 segundos, ou a chamada não terminou em 30. Ela pode ter chegado ao app: não repita às cegas uma tool que escreve. Traz execution_id quando a chamada chegou a ser feita. |
Os erros do próprio app não estão nesta tabela: eles chegam com o status do app e o envelope { error, api_error }, sem error_code.
LLM
Modelos da OpenAI e da Anthropic por uma API só, no formato da API da OpenAI, com o mesmo token do Connect. O SDK oficial da OpenAI funciona sem adaptação: você troca o endereço base e o nome do modelo, e passa a chamar GPT e Claude pelo mesmo código, com stream, tools, imagens e fallback entre modelos.
Como funciona
- Cadastre a sua chave de API da OpenAI ou da Anthropic em POST /merchants/credentials/openai ou /merchants/credentials/anthropic.
- Aponte o SDK da OpenAI para https://connect.reportei.com/api/llm e use o token do Connect como apiKey.
- Chame client.chat.completions.create() com o id de um modelo de GET /llm/models, como openai/gpt-5.4-mini ou anthropic/claude-sonnet-5-5.
Sempre com a sua chave
O Connect nunca chama a OpenAI ou a Anthropic com uma chave do Reportei. Cada chamada usa a chave que você cadastrou, e o consumo é cobrado na sua conta do provedor. Sem a chave do provedor de um modelo, a chamada é recusada com 400 llm_credential_missing. O Connect registra os tokens e o custo estimado de cada chamada, mas não impõe teto de gasto: use os limites da sua conta no provedor.
Com o SDK da OpenAI
Use este endereço como baseURL (base_url no Python) e o token de acesso do Connect como apiKey. client.models.list() e client.chat.completions.create() funcionam como na OpenAI. Não é preciso enviar x-customer-token: o uso é da conta do merchant.
https://connect.reportei.com/api/llmimport OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CONNECT_API_TOKEN,
baseURL: "https://connect.reportei.com/api/llm",
});
const stream = await client.chat.completions.create({
model: "anthropic/claude-sonnet-5-5",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: "Resuma a semana da conta." }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["CONNECT_API_TOKEN"],
base_url="https://connect.reportei.com/api/llm",
)
response = client.chat.completions.create(
model="openai/gpt-5.4-mini",
max_tokens=512,
messages=[{"role": "user", "content": "Resuma a semana da conta."}],
)
print(response.choices[0].message.content)Cadastrar a sua chave
As mesmas rotas de Use sua aplicação, com o provedor no lugar do slug: openai ou anthropic. O corpo é só api_key. A chave fica guardada cifrada e nunca volta numa resposta: o Connect mostra só os 4 últimos caracteres, em api_key_hint. Para trocar a chave, use o PUT; ao removê-la, as chamadas para os modelos do provedor passam a responder 400 llm_credential_missing.
/merchants/credentials/{openai | anthropic}Cadastrar a chave do provedor
Guarda a sua chave de API da OpenAI (openai) ou da Anthropic (anthropic). Uma por provedor: se já houver uma, use o PUT.
{
"api_key": "sk-proj-..."
}/merchants/credentials/{openai | anthropic}Ver a chave cadastrada
Mostra se há chave para o provedor e os 4 últimos caracteres dela. A chave em si nunca volta.
{
"credential": {
"integration": "anthropic",
"api_key_hint": "...9xQz",
"created_at": "2026-11-03T12:00:00.000000Z",
"updated_at": "2026-11-03T12:00:00.000000Z"
}
}/merchants/credentials/{openai | anthropic}Trocar a chave
Substitui a chave do provedor. As próximas chamadas já usam a nova.
{
"api_key": "sk-proj-..."
}/merchants/credentials/{openai | anthropic}Remover a chave
Apaga a chave do provedor. As chamadas para os modelos dele passam a responder 400 llm_credential_missing.
{
"message": "Credential removed"
}Modelos
Lista os modelos que você pode chamar, no formato de GET /v1/models da OpenAI, só dos provedores em que você cadastrou a chave. Cada modelo traz os limites (context_window e max_output_tokens), o que ele atende (supports_tools e supports_vision) e o preço por 1 milhão de tokens. Em long_context vem a faixa de prompt longo, quando o provedor cobra mais acima de um tamanho de entrada: a chamada inteira passa a custar o preço da faixa.
/llm/modelsListar modelos
Os modelos que você pode chamar, só dos provedores com chave cadastrada, com limites, recursos e preço.
{
"data": [
{
"id": "anthropic/claude-haiku-5-5",
"object": "model",
"created": 1792670400,
"owned_by": "anthropic",
"context_window": 1000000,
"max_output_tokens": 128000,
"supports_tools": true,
"supports_vision": true,
"pricing": {
"input_per_mtok": 0.1,
"output_per_mtok": 0.5,
"long_context": {
"above_input_tokens": 100000,
"input_per_mtok": 0.5,
"output_per_mtok": 2.5
}
}
},
{
"id": "openai/gpt-5.4-mini",
"object": "model",
"created": 1792670400,
"owned_by": "openai",
"context_window": 400000,
"max_output_tokens": 128000,
"supports_tools": true,
"supports_vision": true,
"pricing": {
"input_per_mtok": 0.75,
"output_per_mtok": 4.5,
"long_context": null
}
}
],
"object": "list"
}Chat completions
O POST /v1/chat/completions da OpenAI, para os modelos da OpenAI e da Anthropic: mesmo corpo, mesma resposta e mesmo stream. Na resposta, model diz qual modelo respondeu.
/llm/chat/completionsChat completion
Gera a resposta do modelo, em JSON ou em stream. O exemplo do corpo usa fallback para a Anthropic; o curl usa stream (-N para ver os pedaços chegando).
Parâmetros
| Campo | Descrição |
|---|---|
| model | Obrigatório. O id de um modelo de GET /llm/models, no formato provedor/modelo. |
| models | Opcional. Até 3 modelos de fallback, tentados em ordem quando model falha antes do primeiro token. Pode misturar provedores, desde que você tenha a chave de cada um. |
| messages | Obrigatório. As mensagens no formato da OpenAI: system, developer, user, assistant e tool, com texto, partes image_url e tool_calls. |
| max_tokens | Obrigatório em toda chamada (ou max_completion_tokens): é o limite do custo dela. Não pode passar do teto da sua conta (4096 por padrão) nem do max_output_tokens de nenhum modelo da chamada. |
| stream | true para receber a resposta em text/event-stream, pedaço a pedaço. |
| stream_options.include_usage | true para receber, no fim do stream, um pedaço com usage (tokens de entrada e de saída). |
| tools | Funções que o modelo pode chamar, como na OpenAI, com tool_choice. Só nos modelos com supports_tools. |
| response_format | Resposta em JSON (json_object ou json_schema). Só nos modelos da OpenAI. |
| reasoning_effort | O esforço de raciocínio, como na OpenAI. Só nos modelos da OpenAI. |
O stream
Com stream: true, a resposta é text/event-stream: uma linha data: com um pedaço em JSON, uma linha em branco entre eles, e data: [DONE] no fim, como na OpenAI.
data: {"id":"chatcmpl-9f2c4e1a","object":"chat.completion.chunk","created":1792670400,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"role":"assistant","content":"Na semana"},"finish_reason":null}]}
data: {"id":"chatcmpl-9f2c4e1a","object":"chat.completion.chunk","created":1792670400,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"content":", o alcance subiu 12%."},"finish_reason":"stop"}]}
data: [DONE]{
"model": "openai/gpt-5.4-mini",
"models": ["anthropic/claude-haiku-5-5"],
"max_tokens": 512,
"messages": [
{ "role": "system", "content": "Você resume dados de marketing em português." },
{ "role": "user", "content": "Resuma a semana da conta." }
]
}Regras e limites
- max_tokens é obrigatório e tem teto: o menor entre o limite da sua conta e o max_output_tokens de cada modelo da chamada, fallbacks incluídos.
- Cada conta tem, por padrão, até 4 chamadas abertas ao mesmo tempo. Acima disso, a resposta é 429 llm_concurrency_limit: espere uma terminar.
- tools, tool_choice e partes image_url só passam se todos os modelos da chamada atendem (supports_tools e supports_vision em GET /llm/models). Senão, 400 llm_unsupported_field.
- Não são aceitos: n diferente de 1, audio, modalities, prediction, logprobs, top_logprobs e logit_bias. Vazio, null, false e 0 contam como ausentes. O corpo vai até 1 MB.
- O fallback só acontece antes do primeiro token (erro do provedor, limite de requisições ou demora para começar). Depois que a resposta começou, um erro vai para você, e nunca há nova tentativa no mesmo modelo.
- Sem stream, a resposta pode começar com espaços em branco enquanto o modelo gera, para a conexão não cair em respostas longas. Espaço antes do JSON continua sendo JSON válido.
- Um erro depois que a resposta começou não muda o status 200: com stream, chega como data: {"error": {...}} e o stream termina sem data: [DONE]; sem stream, o corpo é o JSON de erro. O SDK da OpenAI levanta um erro nos dois casos.
- Na Anthropic, o Connect traduz system e developer, texto, imagens, tools e tool_calls, tool_choice, stop, max_tokens, stream e usage. temperature, top_p e top_k são ignorados, e response_format só aceita text.
- Na OpenAI, só vão ao provedor os campos que o gateway atende: messages, temperature, top_p, frequency_penalty, presence_penalty, stop, seed, tools, tool_choice, parallel_tool_calls, response_format, reasoning_effort, user e os limites de tokens. Outros, como store, service_tier e metadata, são descartados.
Erros
As recusas destas rotas vêm no formato de erro da OpenAI, que o SDK da OpenAI lê: {"error": {"message", "type", "code"}}. Decida pelo error.code. As recusas de autenticação (401), de pagamento da conta (403 merchant_access_denied) e de limite de requisições (429) são as mesmas do resto da API.
{
"error": {
"message": "This account has no anthropic API key. The gateway only calls a provider with the account's own key: register it with POST /merchants/credentials/anthropic.",
"type": "invalid_request_error",
"code": "llm_credential_missing"
}
}| Status | error.code | Descrição |
|---|---|---|
| 400 | llm_credential_missing | Você não cadastrou a chave do provedor de um modelo da chamada (o model ou um de models). |
| 400 | llm_provider_credential_invalid | O provedor recusou a chave cadastrada. Troque com PUT /merchants/credentials/{provedor}. |
| 400 | llm_model_not_allowed | model ou um de models não está em GET /llm/models, ou models tem mais de 3 itens. |
| 400 | llm_max_tokens_required | Faltou max_tokens (ou max_completion_tokens), ou ele não é um inteiro positivo. |
| 400 | llm_max_tokens_exceeded | max_tokens passou do teto da conta ou do max_output_tokens de um modelo da chamada. |
| 400 | llm_unsupported_field | Um campo que o gateway não aceita, ou tools e imagens num modelo que não atende. |
| 400 | llm_invalid_request | O corpo não é um objeto JSON, ou messages não tem a forma da OpenAI. |
| 403 | llm_not_enabled | O gateway foi desligado para a sua conta. Fale com o suporte. |
| 413 | llm_request_too_large | O corpo passou de 1 MB. |
| 429 | llm_concurrency_limit | A conta já tem o máximo de chamadas abertas. Espere uma terminar. |
| 4XX | llm_provider_error | O provedor recusou o pedido. Vem com o status e a mensagem do provedor. |
| 500 | llm_internal_error | Erro do Connect. Tente de novo. |
| 502 | llm_upstream_error | O provedor falhou e o fallback acabou. Tente de novo mais tarde. |
| 503 | llm_offload_not_configured | O gateway está temporariamente indisponível. Tente de novo mais tarde. |
| 504 | llm_timeout | O modelo demorou demais para responder. |
Webhooks
Cadastre endpoints para receber os eventos das integrações dos seus clientes (token expirado, integração removida, sessão concluída) assim que eles acontecem, sem precisar consultar a API. Toda entrega é um POST assinado, com o mesmo envelope.
Eventos disponíveis
Ao cadastrar um endpoint, você escolhe no campo events quais eventos ele recebe. A lista exata que a sua conta pode assinar vem em available_events, no GET /webhooks/endpoints:
| Evento | Descrição |
|---|---|
| customer_integration.status_updated | Disparado quando o status de uma integração muda (ex: token expirou e a integração precisa ser reconectada). |
| customer_integration.deleted | Disparado quando uma integração de cliente é removida. |
| integration_session.oauth_started | Disparado quando um cliente inicia o fluxo de autenticação OAuth de uma integração. |
| integration_session.oauth_saved | Disparado quando as credenciais OAuth de uma integração são salvas com sucesso. |
| integration_session.completed | Disparado quando uma sessão de integração é concluída. |
Formato do evento
Todo evento chega no mesmo envelope. data.object é o recurso como o GET dele devolve, e em customer_integration.status_updated data.previous_attributes traz o status anterior. Use o id do evento para deduplicar: a entrega é pelo menos uma vez e a ordem não é garantida.
{
"id": "evt_01k7a9x1c3e5g7j9k1m3p5r7t9",
"object": "event",
"type": "customer_integration.status_updated",
"api_version": "v1",
"created": 1791300000,
"livemode": true,
"data": {
"object": {
"uuid": "5d1c3b9a-2e7f-4c1d-8a6b-0f3e9d2c7b14",
"source_id": "17841400000000000",
"source_name": "conta_exemplo",
"status": "expired",
"integration": { "name": "Instagram Business", "slug": "instagram_business" }
},
"previous_attributes": { "status": "active" }
}
}Verificando a autenticidade das notificações
Toda entrega leva o header Connect-Signature no formato t={timestamp},v1={assinatura}. A assinatura é um HMAC-SHA256 de {timestamp}.{corpo} com o secret do endpoint como chave, calculado sobre os bytes do corpo exatamente como chegaram.
Recalcule o HMAC do seu lado, compare em tempo constante com cada v1 (nas 24 horas seguintes a uma troca de secret vêm dois, um de cada secret) e recuse um timestamp com mais de 5 minutos de diferença do seu relógio.
import crypto from "node:crypto";
// rawBody: the request body exactly as received, before any JSON parsing
function isFromConnect(rawBody, signatureHeader, secret) {
const timestamp = signatureHeader.match(/t=(\d+)/)?.[1];
const signatures = [...signatureHeader.matchAll(/v1=([a-f0-9]{64})/g)].map((m) => m[1]);
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false;
}
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
return signatures.some((signature) =>
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
}Entrega e novas tentativas
Responda com qualquer 2xx em até 10 segundos. Outra resposta, um redirect ou nenhuma resposta contam como falha, e o evento é reenviado ao longo de cerca de 3 dias (5 min, 30 min, 2 h, 6 h, 12 h, 24 h e 24 h). Um endpoint que passa 72 horas falhando sem parar é desativado (disabled_reason failures), e responder 410 o desativa na hora (gone). Uma falha que chega mais de 25 horas depois da anterior começa a contagem de novo. Reative com PUT e active true.
/webhooks/endpointsListar endpoints
Retorna os endpoints da conta com o estado de saúde de cada um, os eventos que a conta pode assinar (available_events) e o limite de endpoints.
{
"data": [
{
"id": "9b2f7c1e-4a3d-4f8e-9c21-6d0a5b8e1f34",
"url": "https://sua-aplicacao.com/webhooks/connect",
"description": "Produção",
"events": ["customer_integration.status_updated", "integration_session.completed"],
"active": true,
"disabled_at": null,
"disabled_reason": null,
"consecutive_failures": 0,
"first_failure_at": null,
"last_failure_at": null,
"last_success_at": "2026-10-13 12:00:00",
"created_at": "2026-10-01 09:30:00",
"updated_at": "2026-10-13 12:00:00"
}
],
"available_events": [
"customer_integration.status_updated",
"customer_integration.deleted",
"integration_session.oauth_started",
"integration_session.oauth_saved",
"integration_session.completed"
],
"limit": 5
}/webhooks/endpointsCadastrar endpoint
Cadastra um endereço para receber os eventos escolhidos. A resposta traz o secret, e esta é a única vez que ele aparece.
Parâmetros
| Nome | Descrição |
|---|---|
| url | Endereço https que recebe os eventos (obrigatório). Não pode apontar para rede privada nem repetir uma URL já cadastrada na conta. |
| events | Array com pelo menos um dos eventos acima (obrigatório). |
| description | Um nome para você reconhecer o endpoint (opcional, até 255 caracteres). |
| active | Só na alteração: false para as entregas (disabled_reason manual); true reativa e zera a contagem de falhas, sem reenviar o que se perdeu. |
Cada conta pode ter até 5 endpoints. O secret que assina as entregas só aparece na resposta da criação e na troca de secret: guarde-o na hora.
{
"url": "https://sua-aplicacao.com/webhooks/connect",
"events": ["customer_integration.status_updated", "integration_session.completed"],
"description": "Produção"
}/webhooks/endpoints/{uuid}Consultar endpoint
Retorna o endpoint e a saúde dele: consecutive_failures, first_failure_at, last_success_at e, se ele foi desativado, disabled_at e disabled_reason. Nunca devolve o secret.
{
"webhook_endpoint": {
"id": "9b2f7c1e-4a3d-4f8e-9c21-6d0a5b8e1f34",
"url": "https://sua-aplicacao.com/webhooks/connect",
"description": "Produção",
"events": ["customer_integration.status_updated", "integration_session.completed"],
"active": true,
"disabled_at": null,
"disabled_reason": null,
"consecutive_failures": 0,
"first_failure_at": null,
"last_failure_at": null,
"last_success_at": "2026-10-13 12:00:00",
"created_at": "2026-10-01 09:30:00",
"updated_at": "2026-10-13 12:00:00"
}
}/webhooks/endpoints/{uuid}Alterar endpoint
Altera url, events, description ou active. Envie só os campos que mudam.
{
"active": true,
"events": ["customer_integration.status_updated"]
}/webhooks/endpoints/{uuid}Remover endpoint
Para de entregar na hora, inclusive as tentativas já agendadas, e remove o endpoint.
{
"success": true
}/webhooks/endpoints/{uuid}/rotate-secretTrocar o secret
Gera um secret novo. O anterior continua valendo por 24 horas e, nesse período, cada entrega leva as duas assinaturas.
{
"webhook_endpoint": {
"id": "9b2f7c1e-4a3d-4f8e-9c21-6d0a5b8e1f34",
"secret": "whsec_Hk2m9QpX4tVb7Lw1Zr8yNc5sJ3dF6gA0eUoTiRqWxYz"
}
}/webhooks/endpoints/{uuid}/testEnviar evento de teste
Reenvia agora o evento mais recente da conta do primeiro tipo que o endpoint assina, com id novo e livemode false, e mostra o que o seu endereço respondeu. Se ele responder 2xx, a sequência de falhas do endpoint é zerada; um teste que falha não conta. Limite de 10 testes por minuto.
{
"webhook_delivery": {
"id": "dlv_01k7a9x2m4p6q8r0s2t4v6w8y0",
"event_id": null,
"event_type": null,
"attempt": 1,
"status": "succeeded",
"response_status": 200,
"response_excerpt": "ok",
"error": null,
"duration_ms": 184
}
}/webhooks/endpoints/{uuid}/deliveriesListar entregas
As 20 tentativas mais recentes do endpoint, com status, resposta do seu endereço e erro. O histórico é guardado por 30 dias.
{
"data": [
{
"id": "dlv_01k7a9x2m4p6q8r0s2t4v6w8y0",
"event_id": "evt_01k7a9x1c3e5g7j9k1m3p5r7t9",
"event_type": "customer_integration.status_updated",
"attempt": 2,
"status": "failed",
"response_status": 500,
"response_excerpt": "Internal Server Error",
"error": null,
"duration_ms": 312,
"next_attempt_at": "2026-10-13 12:35:00",
"created_at": "2026-10-13 12:05:00"
}
]
}/webhooks/endpoints/{uuid}/deliveries/{delivery}/redeliverReenviar entrega
Recomeça o ciclo completo de tentativas de um evento. Só a tentativa mais recente do evento pode ser reenviada, e só se ela esgotou (status exhausted); qualquer outra responde 422.
{
"webhook_delivery": {
"id": "dlv_01k7aa0b2c4d6e8f0g2h4j6k8m",
"event_id": "evt_01k7a9x1c3e5g7j9k1m3p5r7t9",
"attempt": 9,
"status": "pending"
}
}Formato anterior: /webhook-subscriptions
As rotas abaixo são o formato anterior, mantido para quem já usa: corpo sem envelope, nomes antigos de eventos (oauth_started, oauth_saved, session_completed) e uma assinatura que não cobre o corpo. Integrações novas devem usar /webhooks/endpoints.
Toda requisição enviada para a sua url cadastrada inclui os headers x-api-token e x-api-signature, permitindo validar que a notificação realmente veio do Reportei Connect antes de processá-la. O header x-api-signature tem o formato t={timestamp},s={assinatura}, onde a assinatura é um HMAC-SHA256 de {timestamp}{client_id} usando o seu client_secret como chave. Recalcule esse hash do seu lado e compare com o valor recebido para confirmar a autenticidade.
/webhook-subscriptionsListar Webhooks
Retorna uma lista paginada dos webhooks cadastrados para o seu merchant.
{
"data": [
{
"id": 12,
"url": "https://sua-aplicacao.com/webhooks/reportei",
"events": ["customer_integration.status_updated", "customer_integration.deleted"],
"is_active": true,
"created_at": "2026-08-05T18:23:55.000000Z",
"updated_at": "2026-08-05T18:23:55.000000Z"
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 1
}
}/webhook-subscriptionsCadastrar Webhook
Cadastra uma nova URL de webhook para o seu merchant. A URL deve usar http ou https e não pode apontar para endereços privados ou internos (ex: localhost, 127.0.0.1, redes 10.x.x.x e 192.168.x.x).
{
"url": "https://sua-aplicacao.com/webhooks/reportei",
"events": ["customer_integration.status_updated", "customer_integration.deleted"],
"is_active": true
}/webhook-subscriptions/{id}Atualizar Webhook
Atualiza parcialmente um webhook já cadastrado. Envie apenas os campos que deseja alterar.
{
"is_active": false
}/webhook-subscriptions/{id}Remover Webhook
Remove um webhook cadastrado do seu merchant.
{
"success": true
}Integrações Disponíveis
Explore as plataformas que podem ser integradas com a API do Reportei Connect: as integrações nativas, de marketing e redes sociais, das quais a API coleta métricas, e os toolkits, que executam ações nos apps do seu cliente.
Como usar as integrações nativas
Conecte a integração: use o endpoint de Customer Integrations para criar uma sessão e conectar a conta do cliente.
Descubra as métricas: chame GET /metrics/{integration_slug} para listar o que a plataforma oferece, ou use o Connect Explorer para testar a coleta sem escrever código.
Consulte os dados: use os endpoints /metrics/get-data ou /metrics/get-data-async com o payload copiado.
Os toolkits seguem outro caminho: você conecta o cliente com toolkit_slug e executa as tools com POST /tools/{tool_slug}/execute. O passo a passo está na seção Toolkits.
Lista de integrações (156)
Nome e slug de cada integração. Nas nativas, o slug é o integration_slug das métricas. Nos toolkits, marcados com a etiqueta Toolkit, é o toolkit_slug, e a lista atual vem em GET /toolkits.
156 integrações encontradas
facebookfacebook_adsinstagraminstagram_businessthreadsgoogle_analyticsgoogle_analytics_4google_adwordssearch_consolegoogle_my_businessgoogle_sheetsgoogle_local_servicesyoutubelinkedinlinkedin_personallinkedin_adstwittertwitter_adstiktoktiktok_businesstiktok_adspinterestpinterest_adsmicrosoft_adsspotify_adskwai_adschatgpt_adsrdstationrd_crmhubspot_marketinghubspot_crmpipedriveactive_campaignmailchimpegoikommophonetrackklaviyogo_high_levelcallrailtwiliokeywordahrefsgreat_pagesshopifywoo_commercenuvem_shophotmarteduzzkiwifymagazordolistifoodrepediuapigoomercardapio_webacuityToolkitagendorToolkitasaasToolkitasanaToolkitattioToolkitaweberToolkitbaremetricsToolkitbeehiivToolkitbitlyToolkitbrevoToolkitcalendlyToolkitcanvaToolkitcapsuleToolkitchatwootToolkitclickupToolkitclockifyToolkitcloudflareToolkitconstant_contactToolkit
conta_azulToolkitcrispToolkitdiscordToolkitdropboxToolkitdubToolkiteventbriteToolkiteverhourToolkitfathomToolkitfigmaToolkitfolkToolkitfreshbooksToolkitfreshdeskToolkitfrontToolkitgetresponseToolkitgithubToolkitgitlabToolkitgmailToolkitgoogle_calendarToolkitgoogle_chatToolkitgoogle_docsToolkitgoogle_driveToolkitgoogle_formsToolkitgoogle_tag_managerToolkitgoogle_tasksToolkitgrafanaToolkitgumroadToolkitinstantlyToolkitintercomToolkitjiraToolkitjotformToolkitkeapToolkitkitToolkitlemon_squeezyToolkitmailjetToolkitmakeToolkitmanychatToolkitmelhor_envioToolkitmentionToolkitmercado_livreToolkitmercado_pagoToolkitmicrosoft_teamsToolkitmiroToolkitmixpanelToolkitnetlifyToolkitnotionToolkitomnisendToolkitonedriveToolkitoutlook_calendarToolkitoutlookToolkitpagerdutyToolkitpandadocToolkitposthogToolkitprintfulToolkitprintifyToolkitrechargeToolkitsalesflareToolkitsalesloftToolkitse_rankingToolkitsegmentToolkitsentryToolkitsharepointToolkitslackToolkitstripeToolkitsupabaseToolkitcodaToolkitsurveymonkeyToolkittallyToolkitteamworkToolkittodoistToolkittogglToolkittypeformToolkitvercelToolkitvimeoToolkitwebexToolkitwebflowToolkitwhatsappToolkitwiseToolkitwistiaToolkitwordpress_comToolkitwrikeToolkitzendesk_sellToolkit
zoomToolkitImplemente o Connect com IA
Copie o prompt e cole no seu agente de código, como Claude Code, Cursor ou Codex. Ele cria a conta, lê a especificação e implementa a integração no seu projeto. Você só preenche o cadastro quando ele pedir.
Prompt para o seu agente
Implemente o Reportei Connect seguindo as instruções em https://app.connect.reportei.com/provision-trial.md. Leia o documento inteiro antes de começar.