| name | unipile-v2-foundations |
| description | Convenção base para qualquer chamada à API Unipile v2 (a que usamos no engine/runtime/daemon). Use SEMPRE que for escrever, revisar ou depurar código que fala com a Unipile — autenticação, base URL, rotas /v2/{account_id}/..., paginação, erros, IDs de provider, segurança de conta e proxy. Leia esta skill antes das específicas (messaging, linkedin-search, posts-and-webhooks). |
Unipile v2 — Fundamentos (padrão do projeto)
A v2 é um redesign real de rotas — NÃO é /api/v1/ nem DSN por conta.
Confirmado empiricamente contra a conta real (OpenAPI 2.16.0).
Base URL, rotas e auth
- Base URL:
https://api.unipile.com — host central, porta 443. Não existe
DSN apiNN.unipile.com:PORTA na v2. O dashboard v2 não mostra DSN algum.
- Rotas:
/v2/{account_id}/... — o account_id vai no PATH, não em query/
body. Ex.: POST /v2/{account_id}/chats/send. Rotas account-level (sem conta):
GET /v2/accounts, PATCH /v2/accounts/{account_id}, POST /v2/auth/link.
- Auth: header
X-API-KEY: {ACCESS_TOKEN} em toda requisição a /v2/*.
- OpenAPI (fonte de verdade):
GET https://api.unipile.com/v2/openapi.json
(com a X-API-KEY). É a spec completa (servers=[api.unipile.com], ~121 rotas).
- ⚠️
https://api.unipile.com/admin/* é a API do dashboard (só cookie de sessão;
rejeita a X-API-KEY com 401). Não é a superfície que usamos.
No código, o cliente é new UnipileProvider('api.unipile.com', apiKey) → fetch
explícito, sem SDK. UNIPILE_DSN no env é opcional (default api.unipile.com).
Paginação
Depende do endpoint. GET /v2/accounts e as buscas Sales Navigator usam
offset/limit (query) e retornam has_more. Alguns endpoints retornam
cursor. Nunca assuma "1 página = tudo"; siga has_more/cursor/offset.
Erros e resiliência
res.ok === false → leia o corpo {object:"Error", type, title, status, req_id}
e propague com contexto (Unipile <method> <path> falhou: <status> <corpo>), como
faz UnipileProvider.
401 api/invalid_credentials = key ausente/errada. 404 api/resource_not_found
(Route Not Found) = rota inexistente — quase sempre path errado (ex.: usar
/api/v1/... legado em vez de /v2/...).
- Webhooks são at-least-once → dedup por id externo (ver posts-and-webhooks).
IDs de provider (LinkedIn) — a pegadinha mais comum
Um usuário tem dois identificadores:
- Public ID: última parte da URL pública (
linkedin.com/in/satyanadella).
- Provider internal ID: o que a API usa nas ações, e é diferente por produto:
Classic
ACo.../ADo..., Sales Navigator ACw..., Recruiter AE....
Converta public → provider ID com GET /v2/{account_id}/users/{identifier} e guarde
o provider_id no prospect (evita reconverter).
Segurança de conta (requisito, não feature) — proxy
Contas de LinkedIn/WhatsApp/Instagram ganham proxy automático da Unipile por
padrão (perto da localização do dono). Padrões obrigatórios:
- O país do proxy AUTOMÁTICO só é definível na CONEXÃO via
POST /v2/auth/link → config.{provider}.auto_proxy_config.country (ISO alpha-2).
Para uma conta já ligada em país errado (ex.: FR p/ dono no BR = risco no LinkedIn),
gere um link de reconexão com account_id + o país certo e reconecte.
PATCH /v2/accounts/{account_id} (additionalProperties:false) aceita SÓ:
proxy: objeto custom {host, port, username?, password?, protocol?} OU
null (remove → volta ao automático).
metadata: { [k]: string }.
- ⚠️ Um
{country} cru é rejeitado — o PATCH não troca o país do auto-proxy.
- Timing randomizado, sem horário fixo; espaçar ações sensíveis. Respeitar
limites do provider; tratar rejeições (parar/adaptar, não martelar).
Onde isso vive no nosso código
engine/src/execution/unipile-provider.ts — cliente HTTP fetch + req() com
X-API-KEY, rotas /v2/{account_id}/....
engine/src/execution/provider.ts / capture/prospect-source.ts — interfaces.
runtime/src/webhook.ts — ingestão de eventos (ver unipile-v2-posts-and-webhooks).
Skills relacionadas: [[unipile-v2-messaging]], [[unipile-v2-linkedin-search]], [[unipile-v2-posts-and-webhooks]].