@trindtech/posts
v0.1.5
Published
Cliente JS/TS pra geração de carrosséis pra Instagram via IA. Integrável em qualquer app via API key.
Maintainers
Readme
@trindtech/posts
Cliente TS/JS pra API do Posties. Geração de carrosséis pra Instagram via IA, em qualquer aplicação.
Instalação
yarn add @trindtech/posts
# ou
npm install @trindtech/posts
# ou
pnpm add @trindtech/postsFunciona em Node.js 18+, edge runtimes (Cloudflare Workers, Vercel Edge), browsers (Vite, webpack, etc — desde que a API key fique no servidor).
Uso
import { PostiesClient } from "@trindtech/posts"
const posties = new PostiesClient({
apiKey: process.env.POSTIES_API_KEY!, // formato: pst_...
// baseUrl: "https://posts.trindtech.com.br", // default; troque em dev
})
// 1. Pega/inicializa a brand da workspace
const brand = await posties.brand.get()
// 2. Atualiza brand (parcial)
await posties.brand.update({
name: "Minha Empresa",
niche: "SaaS B2B",
colorPrimary: "#3b82f6",
})
// 3. Gera ideias de tópicos
const { ideas } = await posties.ideas.generate(10)
// 4. Gera carrossel completo (sync, ~10-30s)
const carousel = await posties.carousels.generate({
topic: "5 erros que travam onboarding de SaaS",
numSlides: 8,
template: "MODERN_BG", // gera bg image via IA
})
// 5. Regenera 1 slide específico OU a imagem
await posties.carousels.regenerate(carousel.id, {
slide: { index: 2, instruction: "deixa mais punchy, com exemplo concreto" },
})
await posties.carousels.regenerate(carousel.id, {
image: { instruction: "tons mais escuros, paleta mais minimalista" },
})
// 6. Lista carrosséis
const list = await posties.carousels.list()
// 7. Atualiza caption/hashtags manualmente
await posties.carousels.update(carousel.id, {
caption: "Minha caption editada",
hashtags: ["#saas", "#onboarding"],
})
// 8. Deleta
await posties.carousels.delete(carousel.id)
// 9. Análise de perfil IG: cola bio + posts e calibra a brand
const insights = await posties.analyze.profile({
bio: "Te ajudo a organizar suas finanças sem planilha.",
samplePosts: [
"Caption do primeiro post...",
"Caption do segundo...",
],
})
console.log(insights.toneAnalysis, insights.suggestedAdjustments)Auth
A API key vai como Authorization: Bearer pst_.... Crie em /settings/api-keys dentro do app Posties.
A key é mostrada apenas uma vez na criação. Guarde em variável de ambiente segura.
Erros
Falhas viram PostiesError com status e message:
import { PostiesError } from "@trindtech/posts"
try {
await posties.carousels.generate({ topic: "..." })
} catch (err) {
if (err instanceof PostiesError) {
console.log(err.status, err.message)
// 401: api key inválida/revogada/expirada
// 422: validação (tópico curto demais, etc)
// 500: falha interna (LLM/Imagen down, etc)
}
}Hooks React (@trindtech/posts/react)
Sub-export que expõe um Provider + hooks do React Query envolvendo o
PostiesClient. Use pra acelerar a integração em apps React (Next.js,
Remix, Vite, etc).
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import {
PostiesProvider,
useBrand,
useCarousels,
useGenerateCarousel,
useRegenerateCarousel,
} from "@trindtech/posts/react"
import { PostiesClient } from "@trindtech/posts"Setup do Provider
⚠️ Crítico de segurança: a API key NUNCA pode ir pro browser. Os
hooks precisam de um PostiesClient — escolha um dos 2 caminhos abaixo
dependendo de onde sua app roda.
Caminho A — Ambiente trusted (Node, scripts, CLI, app interno)
A app inteira roda em ambiente seguro (não tem browser exposto). Cria o client direto com a key:
const postiesClient = new PostiesClient({
apiKey: process.env.POSTIES_API_KEY!,
})
<QueryClientProvider client={queryClient}>
<PostiesProvider client={postiesClient}>
<App />
</PostiesProvider>
</QueryClientProvider>Caminho B — Web app com browser (recomendado pra Next.js / Remix)
A key fica no servidor; o client browser usa um fetch custom que chama uma rota proxy do seu app. O proxy adiciona a key.
1. Cria proxy server-side (Next.js exemplo, em app/api/posties/[...path]/route.ts):
import { NextRequest } from "next/server"
export async function GET(req: NextRequest) {
return proxy(req)
}
export const POST = GET
export const PATCH = GET
export const DELETE = GET
async function proxy(req: NextRequest) {
const path = req.nextUrl.pathname.replace(/^\/api\/posties/, "")
const url = `https://posts.trindtech.com.br/api${path}${req.nextUrl.search}`
const body = req.method === "GET" || req.method === "DELETE"
? undefined
: await req.text()
return fetch(url, {
method: req.method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.POSTIES_API_KEY}`,
},
body,
})
}2. Cria client browser apontando pro proxy:
"use client"
import { PostiesClient } from "@trindtech/posts"
export const postiesClient = new PostiesClient({
apiKey: "ignored", // não usa, key fica no proxy
baseUrl: "/api/posties", // proxy do app
apiPath: "", // CRÍTICO: o proxy já recebe path sem `/api` extra
})
// Detalhe: o SDK ainda tenta mandar `Authorization: Bearer ignored` —
// o proxy ignora e adiciona o seu próprio. Funciona.
//
// `apiPath: ""` evita que o SDK prepende `/api` à URL — sem isso, a
// chamada chega no proxy como `/api/posties/api/autopost/...` (com dup).3. Setup do Provider (igual caminho A):
<QueryClientProvider client={queryClient}>
<PostiesProvider client={postiesClient}>
<App />
</PostiesProvider>
</QueryClientProvider>Hooks disponíveis
function MyApp() {
// Queries — cache automático via React Query
const { data: brand, isLoading } = useBrand()
const { data: carousels = [] } = useCarousels()
const { data: carousel } = useCarousel(carouselId)
// Mutations — invalidam cache relacionado automaticamente no onSuccess
const updateBrand = useUpdateBrand()
const generate = useGenerateCarousel()
const regenerate = useRegenerateCarousel(carouselId)
const updateCarousel = useUpdateCarousel(carouselId)
const deleteCarousel = useDeleteCarousel()
const generateIdeas = useGenerateIdeas()
const analyze = useAnalyzeProfile()
return (
<button
onClick={() => generate.mutate({
topic: "3 erros que matam reserva",
template: "MODERN_BG",
})}
>
{generate.isPending ? "Gerando..." : "Gerar"}
</button>
)
}Cada mutation aceita options (onSuccess, onError) que rodam depois
da invalidação interna de cache:
const generate = useGenerateCarousel({
onSuccess: (carousel) => {
toast.success(`Gerado: ${carousel.id}`)
router.push(`/autopost/${carousel.id}`)
},
})Query keys
Pra invalidação manual:
import { postiesQueryKeys } from "@trindtech/posts/react"
queryClient.invalidateQueries({ queryKey: postiesQueryKeys.brand() })
queryClient.invalidateQueries({ queryKey: postiesQueryKeys.carousels() })
queryClient.invalidateQueries({
queryKey: postiesQueryKeys.carousel(carouselId),
})Peer deps
react ^18 || ^19 e @tanstack/react-query ^5. Marcadas como
optional — se você só usa o SDK vanilla (sem hooks), não precisa
instalar.
⚠️ Aviso pra desenvolvimento local com file: path
Se você está consumindo o SDK via "file:../caminho/do/sdk" (em vez do
npm), yarn copia o node_modules inteiro do SDK pro seu projeto —
incluindo @tanstack/react-query em devDeps. Isso cria duas instâncias
de React Query, causando erro No QueryClient set mesmo com Provider
configurado.
Fix: depois de yarn install, limpa o duplicado:
rm -rf node_modules/@trindtech/posts/node_modulesOu usa via npm (yarn add @trindtech/posts) — peer deps resolvem
corretamente do parent, sem duplicação.
Curl direto
Tudo abaixo funciona sem o SDK (útil pra debug e linguagens sem cliente):
# Lista carrosséis
curl https://posts.trindtech.com.br/api/autopost/carousels \
-H "Authorization: Bearer pst_..."
# Gera novo
curl -X POST https://posts.trindtech.com.br/api/autopost/carousels \
-H "Authorization: Bearer pst_..." \
-H "Content-Type: application/json" \
-d '{"topic":"3 erros que matam onboarding","template":"MODERN_BG"}'Comportamento sync
A geração de carrossel é síncrona — o request bloqueia até o LLM terminar (texto: ~5-10s) e o Imagen renderizar (~10-15s pra MODERN_BG). Total: 10-30s por carrossel.
Pra workloads em batch (>10 carrosséis em sequência), considere paralelizar com Promise.all ou implementar fila no seu lado. Webhooks chegam na v2.
Limites
- Workspaces: cada API key é escopo de UMA workspace. Pra multi-tenant no seu lado, gere uma key por tenant.
- Carrosséis: máx 10 slides cada
- Tópico: 3 a 200 caracteres
- Hashtags: máx 30
Publicar nova versão (mantenedor)
# 1. Bump da versão no package.json (semver)
# 2. Build + publish (prepublishOnly roda build automático)
yarn npm publish --access public
# ou (se o login npm já está feito):
npm publish
# 3. Tag git pra rastreabilidade
git tag -a sdk-v0.0.2 -m "sdk 0.0.2: …"
git push --tagsprepublishOnly garante que o dist/ está atualizado antes de publicar.
Se quiser ver o que será publicado sem subir:
npm pack --dry-run