vek1-api — catálogo de endpoints
Catálogo de endpoints
Todos os endpoints
/internal/*exigem token + (na maioria)X-Actor-User-Idpra ownership check. Endpoints públicos (/chat,/embed,/health,/billing/plans-public,/webhooks/stripe, etc.) ficam atrás do firewall do VPS mas sem token.
Auth scope (X-Auth-Token) — routers/auth.py
Better Auth HTTP backing store. Endpoints purpose-built (não CRUD genérico):
Users
| Path | Adapter call |
|---|---|
POST /internal/auth/users |
createUser |
GET /internal/auth/users/by-id/{id} |
findUserById |
GET /internal/auth/users/by-email/{email} |
findUserByEmail (cached TTL curto) |
PATCH /internal/auth/users/{id} |
patchUser |
DELETE /internal/auth/users/{id} |
deleteUser (204) |
Sessions
| Path | Adapter call |
|---|---|
POST /internal/auth/sessions |
createSession |
GET /internal/auth/sessions/by-token/{token} |
findSessionByToken (cached) |
GET /internal/auth/sessions/by-id/{id} |
findSessionById |
PATCH /internal/auth/sessions/{id} |
patchSession |
DELETE /internal/auth/sessions/{id} |
deleteSession (204) |
DELETE /internal/auth/sessions/by-user/{user_id} |
bulk delete (logout all) |
Accounts
| Path | Adapter call |
|---|---|
POST /internal/auth/accounts |
createAccount |
GET /internal/auth/accounts/by-user/{user_id} |
findAccountsByUser (inclui password hash — fix PR #23) |
GET /internal/auth/accounts/by-provider?provider_id&account_id |
findAccountByProvider |
GET /internal/auth/accounts/password-by-user/{user_id} |
hash lookup pro login |
PATCH /internal/auth/accounts/{id} |
patchAccount |
DELETE /internal/auth/accounts/{id} |
deleteAccount (204) |
Verifications (PKCE, reset password, email verify)
| Path | Adapter call |
|---|---|
POST /internal/auth/verifications |
createVerification |
GET /internal/auth/verifications/by-value/{value} |
findVerificationByValue (PR #22) |
GET /internal/auth/verifications/by-identifier/{identifier} |
findVerificationByIdentifier (PR #22) |
POST /internal/auth/verifications/consume |
consumeOne — DELETE..RETURNING atomic (anti-race) |
DELETE /internal/auth/verifications/by-identifier/{identifier} |
bulk delete |
Misc
| Path | Função |
|---|---|
GET /internal/auth/_cache-stats |
métrica interna (dev) |
App scope (X-Internal-Token + X-Actor-User-Id)
Billing — routers/billing.py (NEW PR #24)
| Path | O que faz |
|---|---|
GET /internal/billing/plans |
Lista planos ativos pra UI signup/upgrade (com stripe_price_id) |
GET /internal/billing/summary |
Sub + settings + usage agregado pra /billing UI |
POST /internal/billing/subscriptions |
Create/update Stripe sub com 2 prices (recurring + metered), proration on switch |
DELETE /internal/billing/subscriptions |
cancel_at_period_end |
POST /internal/billing/setup-intent |
client_secret pro Stripe Elements montar form de cartão |
POST /internal/billing/topup |
PaymentIntent BRL R$30-5000 (off-session se cartão, Elements se sem) |
GET /internal/billing/settings |
Lê company_billing_settings |
PATCH /internal/billing/settings |
Update cap, auto_overage, alert_pct |
GET /internal/billing/usage-this-month |
used + topup_remaining + period |
GET /internal/billing/invoices |
Proxy Stripe.Invoice.list com hosted_invoice_url |
POST /internal/billing/_trigger-usage-report |
Debug: dispara worker manualmente |
POST /internal/billing/_trigger-overage-charge |
Debug: dispara try_charge_overage |
Orders — routers/orders.py
| Path | O que faz |
|---|---|
POST /internal/orders |
Cria order (validate items vs products, dropa fake product_ids) |
GET /internal/orders?store_ids&status&limit&offset |
Lista com filtros |
GET /internal/orders/{id} |
Detalhe + items + events + payment_attempts |
POST /internal/orders/{id}/transition |
State machine transition (valida from → to) |
POST /internal/orders/{id}/attach-proof |
Anexa comprovante (foto/PDF do PIX manual via webhook Evolution) |
GET /internal/orders/stats/{store_id} |
Stats agregadas |
GET /internal/orders/store/{store_id}/payment-settings |
Lê store_payment_settings |
PUT /internal/orders/store/{store_id}/payment-settings |
Patch settings (AbacatePay key, PIX expires, COD, manual PIX) |
POST /internal/orders/{id}/checkout-state |
Salva BR Code + payment_link_url após criar charge no AbacatePay |
POST /internal/orders/{id}/payment-attempt |
Log de tentativa de pagamento |
GET /internal/orders/awaiting-proof-by-lead/{lead_id} |
Busca order awaiting_proof do lead (chamado pelo webhook Evolution pra anexar mídia) |
GET /internal/orders/by-payment-external-id/{charge_id} |
Lookup pelo external_id (chamado pelo AbacatePay webhook) |
Leads — routers/leads.py
| Path | O que faz |
|---|---|
POST /internal/leads/ensure |
Idempotente: cria ou retorna lead por (store_id, channel, external_id) |
GET /internal/leads/{id} |
Detalhe |
GET /internal/leads?store_ids&status&channel&limit&offset |
Lista filtrada (valida ownership por store_ids) |
PATCH /internal/leads/{id} |
Atualiza campos (name, phone, address, custom_attributes, interests, profile_summary, profile_embedding) |
POST /internal/leads/{lead_id}/event |
Adiciona lead_event |
Agents — routers/agents.py
| Path | O que faz |
|---|---|
GET /internal/agents?store_id |
Lista |
GET /internal/agents/{id}/full |
Detalhe + KB + conversations count |
POST /internal/agents |
Cria (com evolution_instance_id gerado backend) |
PATCH /internal/agents/{id} |
Update persona, channels, KB, rate limits |
POST /internal/agents/{id}/toggle-active |
Toggle com regra "1 ativo por store" centralizada |
PUT /internal/agents/{id}/knowledge-base |
Replace KB links (agent_knowledge_base rows) |
GET /internal/agents/{id}/conversations?limit&offset |
Lista conversas do agente |
Webhook GET /webhooks/agents/by-evolution-instance/{instance_id} |
Lookup agent pelo evolution_instance_id (PR #17 inclui company_id no retorno) |
Products + documents + product_files — routers/products.py
| Path | O que faz |
|---|---|
GET /internal/products?store_id&q&limit&offset |
Lista |
GET /internal/products/{id} |
Detalhe |
POST /internal/products/batch-upsert |
Bulk insert/update (wizard CSV) |
GET /internal/products/stats/{store_id} |
Stats |
POST /internal/products/{product_id}/files |
Cria product_file row (após upload MinIO no vek1) |
GET /internal/product-files/{file_id} |
Detalhe |
PATCH /internal/product-files/{file_id}/status |
Update status (processing → completed → error) |
DELETE /internal/product-files/{file_id} |
Delete |
GET /internal/products/{product_id}/files |
Lista files do produto |
POST /internal/documents/upsert-by-product |
Upsert document pelo product_id (caminho wizard) |
GET /internal/documents?store_id&... |
Lista |
GET /internal/documents/{id} |
Detalhe |
POST /internal/documents |
Cria |
PATCH /internal/documents/{id} |
Update |
DELETE /internal/documents/{id} |
Delete (204) |
PUT /internal/documents/{id}/agents |
Replace agent_knowledge_base links pra esse doc |
Stores + company — routers/stores.py
| Path | O que faz |
|---|---|
GET /internal/stores?company_id |
Lista |
GET /internal/stores/{id} |
Detalhe |
POST /internal/stores |
Cria |
PATCH /internal/stores/{id} |
Update |
GET /internal/stores/slug-available/{slug} |
Check |
GET /internal/company |
Lê company do actor |
POST /internal/company/ensure |
Cria se não existir (pós-signup) |
PATCH /internal/company |
Update name/logo/slug |
Dashboard — routers/dashboard.py
| Path | O que faz |
|---|---|
GET /internal/dashboard/{store_id} |
Stats agregadas (orders/leads/products/conversations recentes) |
Messages + audit — routers/messages.py
| Path | O que faz |
|---|---|
POST /internal/messages |
Insert em messages_history |
GET /internal/messages?lead_id|agent_id&limit |
Lista filtrada |
GET /internal/audit?actor_user_id|target_type|target_id&limit |
Lê audit_log (read-only — writes via audit_service em background) |
Token usage — routers/token_usage.py (PR #20)
| Path | O que faz |
|---|---|
POST /internal/token-usage |
Insert (chamado pelo /chat quando registra uso real) |
GET /internal/token-usage?company_id&period_start&period_end |
Lista |
GET /internal/token-usage/summary?company_id&period |
Aggregação por modelo/operation_type |
Stock — routers/stock.py
| Path | O que faz |
|---|---|
POST /internal/stock/decrement-for-order |
Decrement produtos da order (chamado em transition para confirmed) |
POST /internal/stock/restore-for-order |
Restore (chamado em cancellation) |
Webhook POST /webhooks/stock-sync/{store_id} |
Inbound ERP → vek1 (HMAC com inbound_secret da store) |
GET /internal/stock-sync-settings/{store_id} |
Lê settings |
PUT /internal/stock-sync-settings/{store_id} |
Update outbound URL/secret + enabled flags |
POST /internal/stock-sync/test-outbound/{store_id} |
Testa conexão ERP |
Webhook scope (X-Webhook-Token)
| Path | Origem |
|---|---|
POST /webhooks/abacate-pay |
AbacatePay pinga vek1 → vek1 relay com X-Webhook-Token pro vek1-api confirmar PIX |
POST /internal/email/send-reset-password |
Better Auth sendResetPassword → Resend via vek1-api |
POST /webhooks/stock-sync/{store_id} |
ERP customer-owned → vek1-api (HMAC validação inbound_secret) |
Endpoints públicos (sem token)
Conversação + RAG
| Path | O que faz |
|---|---|
POST /chat |
Function calling loop (até 5 iterações). Body {type, message, user_id?, lead_id?, agent_id?, store_id?, conversation_history?, dry_run?}. Suporta knowledgeBase filter por document_ids |
POST /embed |
Ollama bge-m3 (1024 dim). Body {text} ou {texts[]} → {model, dimension, embeddings[][]} |
POST /extract-lead |
DeepSeek JSON mode extraction. Body {messages, existing_profile?, store_context?} → schema completo de campos delta com confidence |
POST /process-file |
Upload PDF/CSV/XLSX → texto extraído + chunks. Resposta inclui performance_metrics |
GET /supported-formats |
Lista formatos aceitos |
Agents config
| Path | O que faz |
|---|---|
GET /agents |
Lista tipos disponíveis com config + preço (do agents_config.yaml) |
GET /agents/{type} |
Detalhe + validation |
POST /agents/reload |
Reload YAML sem restart container |
Billing público (NEW PR #26)
| Path | O que faz |
|---|---|
GET /billing/plans-public |
Lista planos ativos sem auth. Retorna só campos não-sensíveis: id, slug, name, monthly_cents, included_tokens, max_*, overage_per_million_cents. SEM stripe_*_id, SEM timestamps. Usado pela landing /pricing pra evitar TCP direto Postgres. |
Stripe webhook público (NEW PR #24)
| Path | O que faz |
|---|---|
POST /webhooks/stripe |
Signature HMAC validation com STRIPE_WEBHOOK_SECRET. Handlers pros 6 events: customer.subscription.{created,updated,deleted}, invoice.paid, invoice.payment_failed, payment_intent.succeeded |
Health
| Path | O que faz |
|---|---|
GET / |
Info |
GET /health |
Status llm + postgres + ollama + agents configurados. Status degraded se algum down |
Tools registradas no Function Calling (/chat)
search_products(query, limit)— busca semântica via Ollama embedding + pgvector cosineget_product_details(product_id)— SELECT por id (+ filtro store_id)filter_products_by_price(min_price, max_price, category?, limit)— busca semântica + parse regex preçocreate_order(items, payment_method, customer_info)(PR #3) — cria order via HTTP interno (/internal/orders), valida items vs products, dropa fake product_ids (PR #4); retorna BR Code PIX ou instrução COD pra agente devolver ao clienteconfirm_shipping(order_id)— marca shipped (opcional, prompt usa pouco)
check_quota_or_block(company_id) injetado no início de process_query_with_functions (PR #25). BLOCK retorna mensagem genérica "temporariamente indisponível, posso te conectar com atendente". Skip backend persistence em messages_history se lead_id present (vek1 já grava — dd2ac9f).
Pydantic strict (extra='forbid')
Todos modelos de body herdam:
class Strict(BaseModel):
model_config = ConfigDict(extra="forbid")
422 explícito se cliente manda campo desconhecido. Anti-typo + anti-version-drift entre vek1 e vek1-api.
Idempotência
POST /internal/leads/ensure— natural (lookup por(store_id, channel, external_id))POST /internal/company/ensure— natural (1:1 user.id)POST /webhooks/stripe(topup) —stripe_payment_intent_idunique emtoken_topups; INSERT ON CONFLICT DO NOTHINGPOST /internal/billing/topup— Stripe PaymentIntent idempotência via Stripe-Idempotency-Keyreport_usage_to_stripe(worker) —identifier=f"{company_id}:{batch_min_id}:{batch_max_id}"no MeterEvent- Outras POSTs criam novo recurso a cada call — frontend é responsável por dedup