{"info":{"title":"FlySocial API","description":"API para conectar contas do TikTok, Instagram e Pinterest e publicar vídeos (e imagens) automaticamente. Feita para n8n, HTTP e agentes.\n\n## Base URL\n\n`https://flysocial.publishersbr.com`\n\nTodos os caminhos saem daqui. Exemplo: `POST https://flysocial.publishersbr.com/v1/posts`.\n\n## Como funciona\n\n1. Crie uma conta e guarde a chave `fly_sk_…` (só aparece uma vez).\n2. Peça um link OAuth em `GET /v1/connect/{plataforma}` e abra no navegador (o dono da rede social precisa autorizar).\n3. Confira a conta em `GET /v1/accounts` — use o `id` como `accountId`.\n4. Publique com `POST /v1/posts`. A mídia precisa ser uma URL **https pública** (CDN). Google Drive e Dropbox falham.\n5. O POST **espera as redes** e devolve o resultado. Não gravamos o post. Agendamento fica no n8n (Cron / Wait).\n\n## Autenticação\n\nEm todo pedido de `/v1` (exceto cadastro):\n\n`Authorization: Bearer fly_sk_…`\n\nSem chave: `401`. Chave inválida: `401`.\n\n## n8n\n\nUse o nó **HTTP Request**.\n\n- Authentication: Generic Credential Type → Header Auth.\n- Name: `Authorization`. Value: `Bearer fly_sk_…`.\n- Content-Type: `application/json` nos POST.\n- Em `POST /v1/posts`, mande o header `x-request-id` com um UUID **por execução** (não reutilize o mesmo ID em nós diferentes). Se o n8n repetir o mesmo pedido em ~5 minutos, a API devolve o resultado original e **não** republica.\n- O HTTP Request **espera** o upload (pode levar 1–2 min). Não precisa de Wait nem de GET depois. HTTP `200` = todas as redes ok; `207` = alguma falhou — leia `platforms[].errorMessage`. Marque o nó para aceitar 207 se quiser seguir o fluxo.\n- Agende com Cron/Wait do n8n. FlySocial não guarda fila de posts.\n- Conectar uma rede **não** dá para fazer só com o HTTP node: o OAuth abre o site da plataforma. O fluxo é: HTTP GET connect → abrir `url` no browser (ou mandar o link no Telegram/e-mail) → HTTP GET accounts até a conta aparecer.\n\n### Exemplo — publicar agora no TikTok\n\n```json\n{\n  \"content\": \"Lançamento 🚀\",\n  \"media\": [{ \"type\": \"video\", \"url\": \"https://cdn.exemplo.com/clip.mp4\" }],\n  \"platforms\": [{ \"platform\": \"tiktok\", \"accountId\": \"UUID-DA-CONTA\" }]\n}\n```\n\n### Exemplo — as três redes\n\n```json\n{\n  \"content\": \"Receita de 20s\",\n  \"media\": [{ \"type\": \"video\", \"url\": \"https://cdn.exemplo.com/clip.mp4\" }],\n  \"platforms\": [\n    { \"platform\": \"tiktok\", \"accountId\": \"…\", \"tiktok\": { \"privacyLevel\": \"PUBLIC_TO_EVERYONE\", \"allowComment\": true, \"allowDuet\": true, \"allowStitch\": true } },\n    { \"platform\": \"instagram\", \"accountId\": \"…\", \"instagram\": { \"shareToFeed\": true } },\n    { \"platform\": \"pinterest\", \"accountId\": \"…\", \"pinterest\": { \"boardId\": \"123\", \"title\": \"Receita rápida\", \"link\": \"https://site.com/receita\" } }\n  ],\n  \"webhookUrl\": \"https://n8n.exemplo.com/webhook/flysocial\"\n}\n```\n\n## Resultado\n\n| status | HTTP | significado |\n| --- | --- | --- |\n| `published` | 200 | Todas as redes aceitaram |\n| `partial` | 207 | Alguma ok, outra falhou |\n| `failed` | 207 | Todas falharam |\n\n`platforms[].platformPostUrl` vem quando a rede devolve o link. No TikTok, posts privados/em moderação podem não ter URL. Não há `GET /v1/posts/{id}`: o POST já é o resultado.\n\n## Webhook\n\nPOST JSON para `webhookUrl` (do pedido ou da conta) **depois** do mesmo POST — opcional; o corpo da resposta já tem tudo:\n\n```json\n{ \"event\": \"post.published\", \"sentAt\": \"2026-09-18T02:00:00.000Z\", \"data\": { \"status\": \"published\", \"platforms\": [] } }\n```\n\nEventos: `post.published`, `post.partial`, `post.failed`.\n\nCabeçalhos: `x-fly-event`, `x-fly-timestamp` (unix seconds), `x-fly-signature: sha256=…`. A assinatura é HMAC-SHA256 de `{timestamp}.{body}` com `APP_SECRET` do servidor.\n\n## Mídia\n\nA URL precisa: https, pública (sem login), devolver o arquivo (MP4/MOV ou JPEG/PNG), não uma página HTML. Máximo 500 MB. Instagram baixa a URL sozinho; TikTok e Pinterest a API baixa e envia.\n\n## Plataformas\n\n### TikTok\nApp em [TikTok for Developers](https://developers.tiktok.com/docs/en/welcome) com Content Posting API e escopos `user.info.basic`, `video.publish`, `video.upload`. Redirect: `{APP_URL}/oauth/tiktok/callback`. **App sem audit só posta `SELF_ONLY` (privado).** Limite diário da API do TikTok existe; se estourar, a resposta explica.\n\n### Instagram\nConta **Business ou Creator**. App Meta com Instagram Login e `instagram_business_basic` + `instagram_business_content_publish`. Redirect: `{APP_URL}/oauth/instagram/callback`. Vídeo vira Reel. Conta pessoal não publica.\n\n### Pinterest\nAPI v5, escopos `boards:read`, `pins:write`, etc. Redirect: `{APP_URL}/oauth/pinterest/callback`. Todo pin precisa de `boardId` — liste em `GET /v1/accounts/{id}/boards`. Vídeo precisa de capa: `coverImageUrl` ou o frame em `coverImageKeyFrameTime` (padrão 1s).\n\n## Erros\n\nJSON `{ \"error\": \"…\", \"code\": \"…\" }` em português.\n400 pedido inválido · 401 chave · 403 cadastro desligado · 404 não existe · 409 replay/duplicado · 429 calma · 503 plataforma fora ou sem credencial no servidor.\n\n## Extra\n\nResumo para IA: `GET https://flysocial.publishersbr.com/llms.txt`. Spec OpenAPI: `GET https://flysocial.publishersbr.com/docs/json`.","version":"1.0.0"},"servers":[{"url":"https://flysocial.publishersbr.com","description":"Base URL"}],"tags":[{"name":"Conta","description":"Cadastro, chave da API e webhook padrão"},{"name":"Conexão","description":"OAuth das redes — abra o link no navegador"},{"name":"Contas","description":"Redes já conectadas e boards do Pinterest"},{"name":"Posts","description":"Intermediário: publica agora nas redes e devolve o resultado"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"A chave `fly_sk_…` devolvida no cadastro ou em POST /v1/keys. Só aparece uma vez."}},"schemas":{}},"openapi":"3.1.2","paths":{"/v1/meta":{"get":{"tags":["Conta"],"summary":"Ver o que este servidor aceita","description":"Não precisa de chave. Mostra se TikTok/Instagram/Pinterest estão configurados e as Redirect URIs para colar no painel de cada plataforma.","operationId":"getV1Meta"}},"/v1/auth/register":{"post":{"tags":["Conta"],"summary":"Criar conta e chave","description":"Devolve `apiKey` (fly_sk_…) uma única vez. Em produção o cadastro pode estar desligado (`SIGNUP_ENABLED=false`); aí o admin usa `bun run user:create`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password"],"properties":{"name":{"minLength":2,"type":"string"},"email":{"format":"email","type":"string"},"password":{"minLength":8,"type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name","email","password"],"properties":{"name":{"minLength":2,"type":"string"},"email":{"format":"email","type":"string"},"password":{"minLength":8,"type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["name","email","password"],"properties":{"name":{"minLength":2,"type":"string"},"email":{"format":"email","type":"string"},"password":{"minLength":8,"type":"string"}}}}}},"operationId":"postV1AuthRegister"}},"/v1/me":{"get":{"tags":["Conta"],"summary":"Ver a conta da chave","description":"Confirma que a chave vale e devolve e-mail, role e webhook padrão.","security":[{"bearerAuth":[]}],"operationId":"getV1Me"},"patch":{"tags":["Conta"],"summary":"Definir webhook padrão","description":"Ping opcional depois de um POST /v1/posts (o resultado já vem no próprio POST).","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhookUrl"],"properties":{"webhookUrl":{"nullable":true,"description":"URL https que recebe eventos de post. null remove.","type":["string","null"]}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["webhookUrl"],"properties":{"webhookUrl":{"nullable":true,"description":"URL https que recebe eventos de post. null remove.","type":["string","null"]}}}},"multipart/form-data":{"schema":{"type":"object","required":["webhookUrl"],"properties":{"webhookUrl":{"nullable":true,"description":"URL https que recebe eventos de post. null remove.","type":["string","null"]}}}}}},"operationId":"patchV1Me"}},"/v1/keys":{"get":{"tags":["Conta"],"summary":"Listar chaves","description":"Só prefixo e data — o segredo nunca é relido.","security":[{"bearerAuth":[]}],"operationId":"getV1Keys"},"post":{"tags":["Conta"],"summary":"Criar chave","description":"Devolve `apiKey` uma vez. Use uma chave por automação.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"description":"Ex.: n8n produção","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"description":"Ex.: n8n produção","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"description":"Ex.: n8n produção","type":"string"}}}}}},"operationId":"postV1Keys"}},"/v1/keys/{id}":{"delete":{"tags":["Conta"],"summary":"Revogar chave","description":"Pedidos seguintes com essa chave passam a ser 401.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"deleteV1KeysById"}},"/v1/connect/{platform}":{"get":{"tags":["Conexão"],"summary":"Gerar link OAuth","description":"Resposta: `{ url }`. No n8n isso **não** autentica sozinho — alguém precisa abrir o link. O callback desta API grava o token. `returnTo` opcional (https).","security":[{"bearerAuth":[]}],"parameters":[{"name":"platform","in":"path","required":true,"schema":{"type":"string","enum":["tiktok","instagram","pinterest"]}},{"name":"returnTo","in":"query","required":false,"schema":{"description":"https para onde voltar depois do OAuth. http só em localhost.","type":"string"}}],"operationId":"getV1ConnectByPlatform"}},"/v1/accounts":{"get":{"tags":["Contas"],"summary":"Listar redes conectadas","description":"O `id` de cada item é o `accountId` do POST /v1/posts. `status: expired` = precisa conectar de novo.","security":[{"bearerAuth":[]}],"parameters":[{"name":"platform","in":"query","required":false,"schema":{"type":"string","enum":["tiktok","instagram","pinterest"]}}],"operationId":"getV1Accounts"}},"/v1/accounts/{id}":{"delete":{"tags":["Contas"],"summary":"Desconectar rede","description":"Apaga os tokens. Posts antigos continuam visíveis na API.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"deleteV1AccountsById"},"patch":{"tags":["Contas"],"summary":"Definir board padrão do Pinterest","description":"Usado quando o post não manda pinterest.boardId.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["defaultBoardId"],"properties":{"defaultBoardId":{"minLength":1,"type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["defaultBoardId"],"properties":{"defaultBoardId":{"minLength":1,"type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["defaultBoardId"],"properties":{"defaultBoardId":{"minLength":1,"type":"string"}}}}}},"operationId":"patchV1AccountsById"}},"/v1/accounts/{id}/boards":{"get":{"tags":["Contas"],"summary":"Listar boards do Pinterest","description":"Use o `id` do board em `pinterest.boardId` no post.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"getV1AccountsByIdBoards"},"post":{"tags":["Contas"],"summary":"Criar board no Pinterest","description":"Também vira o board padrão desta conta.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"type":"string"},"description":{"type":"string"},"privacy":{"type":"string","enum":["PUBLIC","PROTECTED","SECRET"]}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"type":"string"},"description":{"type":"string"},"privacy":{"type":"string","enum":["PUBLIC","PROTECTED","SECRET"]}}}},"multipart/form-data":{"schema":{"type":"object","required":["name"],"properties":{"name":{"minLength":1,"type":"string"},"description":{"type":"string"},"privacy":{"type":"string","enum":["PUBLIC","PROTECTED","SECRET"]}}}}}},"operationId":"postV1AccountsByIdBoards"}},"/v1/posts":{"post":{"tags":["Posts"],"summary":"Publicar agora","description":"Intermediário: não guardamos o post. O pedido espera o TikTok/Instagram/Pinterest e devolve `platforms[]` com URL ou erro. HTTP 200 = todas ok; 207 = alguma falhou. No n8n, o próprio HTTP Request já traz o resultado — não há fila nem GET /v1/posts/{id}. Agende no n8n (Wait/Cron). Header `x-request-id` (UUID por execução) evita republicar se o n8n repetir o mesmo POST em ~5 minutos.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["media","platforms"],"properties":{"content":{"maxLength":4000,"description":"Legenda. No Pinterest vira description (800) e a 1ª linha pode virar title.","type":"string"},"media":{"minItems":1,"maxItems":4,"type":"array","items":{"type":"object","required":["url"],"properties":{"type":{"type":"string","enum":["video","image"]},"url":{"minLength":8,"description":"URL https pública do arquivo. Não use Drive/Dropbox.","type":"string"}}}},"platforms":{"minItems":1,"maxItems":6,"type":"array","items":{"type":"object","required":["platform","accountId"],"properties":{"platform":{"type":"string","enum":["tiktok","instagram","pinterest"]},"accountId":{"format":"uuid","description":"id de GET /v1/accounts","type":"string"},"tiktok":{"type":"object","properties":{"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"allowComment":{"type":"boolean"},"allowDuet":{"type":"boolean"},"allowStitch":{"type":"boolean"},"draft":{"description":"true envia para a inbox do TikTok em vez de publicar.","type":"boolean"},"isAigc":{"type":"boolean"},"brandContent":{"type":"boolean"},"brandOrganic":{"type":"boolean"},"coverTimestampMs":{"type":"number"}}},"instagram":{"type":"object","properties":{"shareToFeed":{"description":"Reel também no feed. Padrão true.","type":"boolean"},"coverUrl":{"description":"Capa https pública do Reel.","type":"string"}}},"pinterest":{"type":"object","properties":{"boardId":{"description":"Obrigatório se a conta não tem board padrão.","type":"string"},"boardSectionId":{"type":"string"},"title":{"maxLength":100,"type":"string"},"link":{"description":"Link de destino https, sem encurtador.","type":"string"},"coverImageUrl":{"type":"string"},"coverImageKeyFrameTime":{"description":"Segundo do vídeo usado como capa.","type":"number"}}}}}},"webhookUrl":{"description":"Opcional. Também enviamos o resultado para esta URL. O POST já devolve o resultado.","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["media","platforms"],"properties":{"content":{"maxLength":4000,"description":"Legenda. No Pinterest vira description (800) e a 1ª linha pode virar title.","type":"string"},"media":{"minItems":1,"maxItems":4,"type":"array","items":{"type":"object","required":["url"],"properties":{"type":{"type":"string","enum":["video","image"]},"url":{"minLength":8,"description":"URL https pública do arquivo. Não use Drive/Dropbox.","type":"string"}}}},"platforms":{"minItems":1,"maxItems":6,"type":"array","items":{"type":"object","required":["platform","accountId"],"properties":{"platform":{"type":"string","enum":["tiktok","instagram","pinterest"]},"accountId":{"format":"uuid","description":"id de GET /v1/accounts","type":"string"},"tiktok":{"type":"object","properties":{"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"allowComment":{"type":"boolean"},"allowDuet":{"type":"boolean"},"allowStitch":{"type":"boolean"},"draft":{"description":"true envia para a inbox do TikTok em vez de publicar.","type":"boolean"},"isAigc":{"type":"boolean"},"brandContent":{"type":"boolean"},"brandOrganic":{"type":"boolean"},"coverTimestampMs":{"type":"number"}}},"instagram":{"type":"object","properties":{"shareToFeed":{"description":"Reel também no feed. Padrão true.","type":"boolean"},"coverUrl":{"description":"Capa https pública do Reel.","type":"string"}}},"pinterest":{"type":"object","properties":{"boardId":{"description":"Obrigatório se a conta não tem board padrão.","type":"string"},"boardSectionId":{"type":"string"},"title":{"maxLength":100,"type":"string"},"link":{"description":"Link de destino https, sem encurtador.","type":"string"},"coverImageUrl":{"type":"string"},"coverImageKeyFrameTime":{"description":"Segundo do vídeo usado como capa.","type":"number"}}}}}},"webhookUrl":{"description":"Opcional. Também enviamos o resultado para esta URL. O POST já devolve o resultado.","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["media","platforms"],"properties":{"content":{"maxLength":4000,"description":"Legenda. No Pinterest vira description (800) e a 1ª linha pode virar title.","type":"string"},"media":{"minItems":1,"maxItems":4,"type":"array","items":{"type":"object","required":["url"],"properties":{"type":{"type":"string","enum":["video","image"]},"url":{"minLength":8,"description":"URL https pública do arquivo. Não use Drive/Dropbox.","type":"string"}}}},"platforms":{"minItems":1,"maxItems":6,"type":"array","items":{"type":"object","required":["platform","accountId"],"properties":{"platform":{"type":"string","enum":["tiktok","instagram","pinterest"]},"accountId":{"format":"uuid","description":"id de GET /v1/accounts","type":"string"},"tiktok":{"type":"object","properties":{"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"allowComment":{"type":"boolean"},"allowDuet":{"type":"boolean"},"allowStitch":{"type":"boolean"},"draft":{"description":"true envia para a inbox do TikTok em vez de publicar.","type":"boolean"},"isAigc":{"type":"boolean"},"brandContent":{"type":"boolean"},"brandOrganic":{"type":"boolean"},"coverTimestampMs":{"type":"number"}}},"instagram":{"type":"object","properties":{"shareToFeed":{"description":"Reel também no feed. Padrão true.","type":"boolean"},"coverUrl":{"description":"Capa https pública do Reel.","type":"string"}}},"pinterest":{"type":"object","properties":{"boardId":{"description":"Obrigatório se a conta não tem board padrão.","type":"string"},"boardSectionId":{"type":"string"},"title":{"maxLength":100,"type":"string"},"link":{"description":"Link de destino https, sem encurtador.","type":"string"},"coverImageUrl":{"type":"string"},"coverImageKeyFrameTime":{"description":"Segundo do vídeo usado como capa.","type":"number"}}}}}},"webhookUrl":{"description":"Opcional. Também enviamos o resultado para esta URL. O POST já devolve o resultado.","type":"string"}}}}}},"operationId":"postV1Posts"}}}}