跳转至

本页暂时只有英文版

API Routes

All routes prefixed with /api/. When auth_required is enabled (see Settings), all routes except /api/auth/status, /api/auth/login, /api/health, and /api/sse require JWT auth via httpOnly cookie. When auth_required is disabled (default), all routes return the default admin user without authentication.

auth.py — Authentication

Method Path Description
GET /api/auth/status Public. Returns { auth_required: bool }
POST /api/auth/login Login with identifier (username or email) + password; sets httpOnly cookie. If identifier contains @, matches by email; otherwise by username.
POST /api/auth/logout Clear auth cookie
GET /api/auth/me Return current user (includes username, nullable email)
PATCH /api/auth/me/password Change own password (current_password + new_password)
PATCH /api/auth/me/profile Update own profile: username, name, email (nullable, EmailStr), plan_mode_default. Admin can change email; send null to clear email.

app_settings.py — App Settings

Method Path Description
GET /api/settings Return app settings (public). Also carries server capabilities that are not stored settings: max_agent_concurrency, default_schedule_timezone, install_id, and max_upload_size (bytes, from MAX_UPLOAD_SIZE in app/uploads.py — the browser reads this instead of hardcoding a cap; see Upload limits)
PUT /api/settings Update settings (admin only). Keys: auth_required, max_agent_concurrency (int, 1-10, live-updates semaphore), default_schedule_phase_mode (fanout | single — default mode pre-selected when creating new all-stores schedules), default_schedule_timezone (IANA name, validated via ZoneInfo; GET seeds with get_server_timezone() when unset), task_retention_days (int, 0-3650, default 30; controls the daily auto-cleanup job — 0 disables it. See docs/tasks.md § Task deletion + auto-cleanup), google_workspace_enabled
GET /api/settings/google-workspace/status Report gws binary presence, auth status, version, and whether the umbrella bundle is enabled/installed
POST /api/settings/google-workspace/enable Admin only. Validates prereqs (400 if gws missing or unauthenticated), runs gws generate-skills, installs the 19-skill umbrella at .claude/skills/gws/, sets google_workspace_enabled=true
POST /api/settings/google-workspace/disable Admin only. Idempotent. Removes .claude/skills/gws/ and clears the flag

users.py — User Management (admin only)

Method Path Description
GET /api/users List all users
POST /api/users Create user (username required, name, email optional/validated, password, role)
PUT /api/users/{id} Update user (supports username, email; send email: null to clear)
DELETE /api/users/{id} Soft-delete (set is_active=false)

stores.py — Store Management

Method Path Description
GET /api/stores List all stores
POST /api/stores Create a store
GET /api/stores/{store_id} Get store by ID
DELETE /api/stores/{store_id} Delete a store

tasks.py — Task Management

Method Path Description
GET /api/tasks?store_id=&include_archived=&parent_task_id= List tasks (filter by store, archived, or parent task)
POST /api/tasks Create task (auto mode by default; plan_mode=true for plan mode; optional parent_task_id to create a sub-task — sub-tasks must have a store_id)
GET /api/tasks/{task_id} Get task by ID
DELETE /api/tasks/{task_id} Cascade-delete a task and its full subtree (children, grandchildren, …). Removes dependent rows (task_steps, task_messages, task_attachments, task_logs, screenshots), drops the on-disk workspace at ~/.vibe-seller/tasks/{task_id}/, and best-effort stops any running agent. Refuses with 409 if any task in the subtree is in designing / running — caller must Stop first. Idempotent: returns {ok: true} even when the task does not exist. Cleanup logic lives in app/task_delete.py and is shared with the auto-cleanup job.
POST /api/tasks/{task_id}/retry Retry failed/completed task (clears plan_history, wipes workspace)
POST /api/tasks/{task_id}/start Queue task via scheduler
GET /api/tasks/{task_id}/steps Get task steps
POST /api/tasks/{task_id}/agent/start Start Claude Code agent
POST /api/tasks/{task_id}/agent/stop Stop agent
POST /api/tasks/{task_id}/design Start design agent
POST /api/tasks/{task_id}/execute-plan Execute planned task (plan mode only)
POST /api/tasks/{task_id}/questions/answer Answer agent question (batch)
POST /api/tasks/{task_id}/result Record task result summary (agent-only via MCP vibe_seller_set_task_result). Does NOT transition status; _auto_run_task handles that.
POST /api/tasks/{task_id}/error Record unrecoverable task error (agent-only via MCP vibe_seller_set_task_error). Does NOT transition status; _auto_run_task sees task.error during cleanup and transitions to FAILED.
POST /api/tasks/{task_id}/ad-declaration Declare what THIS PHASE of an ad task is for, before any browser work (agent-only via MCP vibe_seller_declare_ad_task). Body: {kind, scope} where kind ∈ audit/create/execute/investigate — there is no edit; "change these three bids" is a one-campaign audit. scope = {combos[], campaigns[], products[]}; absent combos means every marketplace the store sells on. Append-only: a second declaration is 409 unless a NEW user message has arrived since the last one, so only the person typing can open a phase. Two exceptions, both ratchets (ad_declaration.supersedes) — within a turn you may narrow at the same kind (recording campaign ids only enumeration could reveal), or correct investigate → audit at the same-or-narrower reach. The latter is legal because it adds obligation (coverage + a console); audit → investigate and every other pair stay 409, as does any widening. On accept, mirrored to AD_TASK.json in the task workspace for the stop gates (the row stays authoritative).
GET /api/tasks/{task_id}/ad-declarations Every declaration this task has made, oldest first. The frontend binds each result item to the declaration in force when it landed: kind == 'audit' opens the review console, and its scope bounds which campaigns are actionable.
GET /api/tasks/{task_id}/schedule-state/{key} Read a cross-run cursor persisted by a prior run of the same schedule (agent-only via MCP vibe_seller_get_schedule_state). Scope resolved server-side from task.schedule_id; non-scheduled tasks → 400. Response omits schedule_id so the MCP tool output cannot leak it to the agent. Keys must match [A-Za-z0-9_.-]{1,64}.
PUT /api/tasks/{task_id}/schedule-state/{key} Upsert a cross-run cursor (agent-only via MCP vibe_seller_set_schedule_state). Body: {value: string} (non-empty). Uses SQLite INSERT ON CONFLICT DO UPDATE so concurrent manual triggers can't race the PK insert. Same scoping / key rules as GET. Typed keys: email_watermark must be a unix epoch seconds integer string (regex ^[0-9]{1,15}$) — ISO timestamps are rejected because lex compare with microseconds / timezone variants is unsafe.
POST /api/tasks/{task_id}/wake Wake a waiting task (optional message)
GET /api/tasks/{task_id}/messages Get agent chat history
POST /api/tasks/{task_id}/messages Send chat message to agent; if task is in waiting status, also wakes the task (response includes woken: true)
GET /api/tasks/{task_id}/files List agent-generated files in task workspace (non-symlink, non-dot files in task root)
GET /api/tasks/{task_id}/files/{filename} Download a task output file (path traversal protected)

TaskResponse includes created_by_name: str | null — the creator's display name resolved from the users table (defaults to "admin" if not found).

schedules.py — Schedule Management

Method Path Description
GET /api/schedules?store_id= List schedules (filter by store)
POST /api/schedules Create a recurring schedule. Optional phase_mode (fanout | single) only honored when store_id is null; store-bound schedules always resolve to single. When omitted, server uses AppSettings.default_schedule_phase_mode, falling back to fanout. two_phase is system-only and rejected with 400. Optional fanout_serial (bool, default false) runs the fanout's stores one at a time (see All-Stores Scheduling); 400 when set on a store-bound or single schedule. When plan_mode=true, the response is returned with plan_status='planning' and a fresh is_plan_only Task is spawned; the schedule is not eligible to fire until the user approves the plan (see Plan-at-creation lifecycle).
GET /api/schedules/{id} Get schedule detail
PUT /api/schedules/{id} Update schedule. Mutable: title, description, schedule_type, schedule_time, schedule_day, interval_value, timezone, ai_profile_id, finalize_description, fanout_serial (400 on a store-bound or single schedule; omitted leaves it unchanged). plan_mode, phase_mode, and store_id are immutable (400 / 422 respectively). plan_version acts as an If-Match optimistic-lock token — stale values return 412. On a plan-mode schedule, a description change (normalized whitespace) aborts any in-flight planner and flips plan_status='stale'; call /replan to author a new plan.
DELETE /api/schedules/{id} Delete schedule
POST /api/schedules/{id}/pause Pause schedule
POST /api/schedules/{id}/resume Resume schedule. Returns 409 if plan_mode=true and plan_status != 'ready' (gate with the cron fire-gate so APScheduler doesn't fire a blocked schedule).
GET /api/schedules/{id}/tasks List child tasks for schedule
POST /api/schedules/{id}/trigger Manually trigger schedule. Returns 409 if plan_mode=true and plan_status != 'ready'. All-stores schedules route by phase_mode: fanout/two_phase → one task per active store; single → one no-store task. Store-bound schedules always create one store-scoped task. Before creating a per-store child task, any prior waiting task for the same (schedule, store) is cancel-forwarded (FAILED with error_category='superseded_by_next_fire') and a schedule_waiting_cancelled SSE event is emitted.
POST /api/schedules/{id}/replan Idempotent. Spawn a new is_plan_only Task to author the schedule's plan. If a non-terminal planning Task already exists, returns the current response without spawning a duplicate. Returns 400 for non-plan-mode schedules.

ScheduleResponse fields

All endpoints returning a schedule serialize these fields:

Field Type Notes
core CRUD fields — id, store_id, title, description, plan, schedule_type, schedule_time, schedule_day, interval_value, timezone, is_active, is_system, phase_mode, plan_mode, ai_profile_id, created_by, created_at, updated_at
plan_status 'none' \| 'planning' \| 'ready' \| 'stale' \| 'failed' Lifecycle of the frozen plan — see PlanStatus.
plan_version int Monotonic counter, bumped on every successful plan commit. Use as If-Match when sending PUT.
plan_error string | null Last planner failure reason (shown in the UI banner on failed). Cleared on successful plan commit or on prompt-edit invalidation.
current_planning_task_id string | null Task ID currently authoring the plan, if any.
next_run string | null Next APScheduler fire time (ISO).
child_task_count int Count of non-plan-only child tasks.
last_run_status string | null Status of the most recent non-plan-only child task.
pending_questions_count int Count of child tasks in waiting (frontend badges this).

ScheduleUpdate body

PUT accepts the mutable fields listed in the table above plus optional plan_version (for the If-Match check). Unknown fields return 422 (model_config = extra='forbid').

On create, if the request body omits timezone (or sends null), the router resolves it in order: AppSettings['default_schedule_timezone'] → get_server_timezone() (via tzlocal, UTC fallback). PUT accepts timezone and re-registers the APScheduler job with the new zone. Invalid IANA strings → 400.

events.py — Event Tracking

Method Path Description
GET /api/events?status=&store_id= List events (multi-status filter, comma-separated)
POST /api/events Create event manually
GET /api/events/{id} Get event
PUT /api/events/{id} Update event fields
POST /api/events/{id}/status Change status with transition validation
POST /api/events/{id}/confirm Confirm draft → open
POST /api/events/{id}/dismiss Dismiss event
POST /api/events/{id}/sync Sync to external backend
DELETE /api/events/{id} Delete event
GET /api/events/{id}/activities List activity timeline
POST /api/events/{id}/activities Add note to timeline
GET /api/events/backends List sync backends
POST /api/events/backends/configure Configure sync backend

email_accounts.py — Email Account Management

Method Path Description
GET /api/email-accounts List email accounts
POST /api/email-accounts Create email account (password encrypted)
GET /api/email-accounts/discover Auto-discover IMAP settings
GET /api/email-accounts/discover-smtp Auto-discover SMTP settings
POST /api/email-accounts/{id}/test Test IMAP connection
POST /api/email-accounts/{id}/test-smtp Test SMTP connection
POST /api/email-accounts/{id}/send Send email via SMTP
DELETE /api/email-accounts/{id} Delete email account
GET /api/email-accounts/info-by-store/{store_id} DB paths + schema for agent MCP tools
GET /api/stores/{id}/emails List linked emails for store
POST /api/stores/{id}/emails Link email account to store
DELETE /api/stores/{id}/emails/{link_id} Unlink email from store
POST /api/stores/{id}/emails/poll Poll emails (deprecated, use background sync)

profiles.py — AI Agent Profiles

Method Path Description
GET /api/profiles List AI profiles
POST /api/profiles Create AI profile
PUT /api/profiles/{id} Update AI profile
DELETE /api/profiles/{id} Delete AI profile
PATCH /api/profiles/{id}/set-default Set a profile as the user's default. Response: {'ok', 'default_profile_id', 'schedules_synced'} — schedules_synced counts the caller's pinned schedules re-pinned to the new default (only when the caller's sync_profile_to_schedules pref is on; see subsystems.md § Schedule AI profile resolution)
POST /api/profiles/validate Probe a profile's endpoint config (no persistence)
GET /api/profiles/presets Provider presets + per-provider model options

POST /api/profiles/validate takes {"env": {...}} and makes one minimal Anthropic /v1/messages request against the config's own ANTHROPIC_BASE_URL (mirroring Claude Code's auth). It always returns HTTP 200 — the verdict is in the body: {"ok": bool, "code": str, "error": str, "reported_model": str | null}. The Settings UI hits it on save so an unreachable base URL, a wrong/expired key, a wrong protocol, or a retired model id is caught on the config page rather than on the next agent run. See backend.md § Profile Endpoint Validation.

workspace.py — Workspace & Knowledge & Skills

Method Path Description
POST /api/workspace/knowledge/sync Fetch remote knowledge updates
GET /api/workspace/knowledge/sync-meta Get sync metadata
POST /api/workspace/skills/sync Sync built-in skills (local + remote)
GET /api/workspace/skills/sync-meta Get skills sync metadata
POST /api/workspace/skill Create a new user skill
GET /api/workspace/structured Workspace grouped for the UI. One entry per store joining stores/<slug>/ knowledge files with store-data/<slug>/ run-data data_files/data_path (run-data sorted newest-first)
GET /api/workspace/file Read a text file ({path, content}); decodes utf-8 with GB18030 fallback; 400 with a pointer to /file/raw for binary
GET /api/workspace/file/raw Raw bytes with guessed content type. Content-Disposition: inline only for non-scriptable types (pdf, png/jpeg/gif/webp); everything else attachment (stored-XSS guard)
GET /api/stores/{id}/bookmarks Read Chrome profile bookmarks

workspace_assistant.py — Workspace AI Assistant

Method Path Description
POST /api/workspace/assistant/message Send message to workspace AI assistant
POST /api/workspace/assistant/stop Stop workspace assistant session
GET /api/workspace/assistant/status Check if assistant is running

dida365_oauth.py — TickTick/Dida365 Integration

Method Path Description
GET /api/dida365/status TickTick/Dida365 connection status
POST /api/dida365/authorize Start OAuth2 flow
GET /api/dida365/callback OAuth2 redirect callback
GET /api/dida365/projects List TickTick projects
POST /api/dida365/configure Save project + MCP path
POST /api/dida365/setup-mcp Auto-install ticktick-mcp
DELETE /api/dida365/disconnect Disconnect integration

chat_bots.py — Chat Bot Webhooks (WeCom / DingTalk / Feishu / Slack)

Workspace-level webhook configs for group chat bots. Not bound to any store. Four platforms are supported: WeCom / WeChat Work (企业微信), DingTalk (钉钉), Feishu / Lark (飞书) and Slack.

The platform is derived by the server from the webhook URL host — it is never accepted from the client, so a stored bot can never disagree with its own URL and a signing secret can never be posted to a host the user did not choose. URLs must be https with an exact host match (qyapi.weixin.qq.com, oapi.dingtalk.com, open.feishu.cn / open.larksuite.com, hooks.slack.com / hooks.slack-gov.com) plus the platform's path prefix; anything else (cleartext, look-alike hosts like oapi.dingtalk.com.evil.com, userinfo tricks like https://oapi.dingtalk.com@evil.com/...) → 400. The single exception: a LOOPBACK host (strict ipaddress loopback or localhost) is accepted — plain http included — only while the server process runs with VIBE_ALLOW_LOOPBACK_WEBHOOKS=1, which the offline e2e suite (CI and docker/e2e) uses to register a loopback stub webhook; production leaves it unset. Send endpoints (/test, /send, /send-file) re-derive the platform from the stored URL and refuse with 400 when it no longer matches, so a row saved before this validation cannot send to a non-platform host.

List responses mask webhook_url (the URL is the secret) and report only has_secret — use the single-item GET to retrieve the full URL + secret for the edit form. The keyword is returned in the list too: it is not a secret (the UI shows it on the row), and it never reaches the agent — keyword handling is entirely server-side.

Method Path Description
GET /api/chat-bots/platforms Registry metadata the UI renders from: {name, label, hosts, path_prefix, uses_secret, supports_file, max_message, limits, limit_unit, doc_url} per platform
GET /api/chat-bots List bots — {id, name, platform, webhook_url_masked, has_secret, can_send_file, created_at, updated_at}; the mask shows the host + last 4 chars of the secret-bearing part (e.g. https://hooks.slack.com/...****abcd)
GET /api/chat-bots/{id} Get one bot with the full webhook_url and secret (for edit)
POST /api/chat-bots Create {name, webhook_url, secret?, keyword?}. secret is only valid for DingTalk (加签) / Feishu (签名校验) — non-empty on WeCom/Slack → 400; keyword only for DingTalk (自定义关键词) / Feishu (关键词) — the SERVER appends it to any outgoing message that lacks it, so agents and the Test button work unchanged
PUT /api/chat-bots/{id} Update name, webhook_url, secret or keyword; null/blank required fields → 400; changing the URL re-derives platform (and drops a secret/keyword the new platform cannot use); "" clears a secret or keyword
DELETE /api/chat-bots/{id} Delete bot
POST /api/chat-bots/{id}/test Post a test message to the webhook; optional {content} (blank/whitespace → default, which starts with "Vibe Seller" — matters for DingTalk keyword security)
POST /api/chat-bots/{id}/send Post a real message: {content, msgtype?}. msgtype is text (default) or markdown; blank content / unknown msgtype → 400. Pre-send size check per platform (WeCom 4096 B, DingTalk 20000 B, Feishu 20000 B body, Slack 40000 chars text / 12000 markdown) rejects without a network call. Used by agents via the MCP bridge (see docs/workspace.md).
POST /api/chat-bots/{id}/send-file Upload a local file and post it as a file message: {path}. WeCom only — other platforms → 400 before path resolution. path is expanded (~) and must be absolute under an allowed root (/tmp, /private/tmp, or ~/.vibe-seller/downloads) — anything else → 400 (blocks exfiltrating the DB/secrets/~/.ssh). File must be 6 B–20 MB (WeCom limit). Returns {ok, message}. Used by agents via the MCP tool vibe_seller_send_chat_file.

Platform notes

| Platform | Text | Markdown | File | Signing | Keyword | Limits | |---|---|---|---|---|---| | WeCom 企业微信 | {"msgtype":"text","text":{"content":…}} | {"msgtype":"markdown","markdown":{"content":…}} (reduced subset) | Yes (upload_media → media_id → msgtype=file) | — | — | 4096 B / msg | | DingTalk 钉钉 | {"msgtype":"text","text":{"content":…}} | {"msgtype":"markdown","markdown":{"title":…,"text":…}} — title derived from the first non-empty line | No | Optional 加签: HMAC-SHA256(key=secret, msg="<ts-ms>\n<secret>") → base64 → URL-encoded, sent as &timestamp=&sign= query params; combinable with a keyword | Optional 自定义关键词 — server appends it to any message that lacks it | 20000 B / msg, 20 msg/min | | Feishu / Lark 飞书 | {"msg_type":"text","content":{"text":…}} | Interactive card (msg_type=interactive, card JSON 2.0, markdown element) | No | Optional 签名校验: HMAC-SHA256(key="<ts-s>\n<secret>", msg empty) → base64 (NOT URL-encoded), sent as top-level JSON body timestamp/sign | Optional 关键词 — server appends it to any message that lacks it | 20 KB body, 100 req/min, 5 req/s | | Slack | {"text": …} with &<> escaped | Block Kit {"text": <summary>, "blocks":[{"type":"markdown","text":…}]} (falls back to a standard→mrkdwn converter if the block is rejected) | No | — (the URL is the secret) | — | 40000 chars text / 12000 markdown |

Backend senders live in app/notifiers/ — base.py holds the PLATFORMS registry, detect_platform, shared error handling and the size guard; one module per platform (wecom.py, dingtalk.py, feishu.py, slack.py). Signing helpers are pure functions taking an explicit timestamp (unit-tested against independent reference implementations). Errors are logged server-side; the response message never echoes the URL back to the client.

sse.py — Server-Sent Events

Method Path Description
GET /api/sse SSE stream of all real-time events

Note: SSE endpoint was renamed from /api/events to /api/sse to free up /api/events for event tracking.

browser.py — Browser / Ziniao Utilities

Method Path Description
GET /api/ziniao/launcher Download ziniao_webdriver.bat
POST /api/browser/web/start Start (or reuse) the store-less orchestrator web browser. Lazy-called by the bin/_web wrapper on first use; parallels POST /api/stores/{id}/browser/start. Accepts force for parity (no-op).

system.py — Server Runtime Metadata

Method Path Description
GET /api/system/info Return { platform: 'mac' \| 'windows' \| 'wsl' \| 'linux', version: str, commit: str \| null }. platform is the host OS; version is the package version from importlib.metadata ('0.0.0+dev' for editable / dev installs); commit is the short git SHA when the source tree is a git checkout (useful when version is the dev fallback). Frontend fetches this once on mount and uses it as the single source of truth for platform-conditional UI (Ziniao launcher download, install hints, etc.) and the about/footer build stamp.
GET /api/system/update-check Check PyPI for a release newer than the one running here. Dev/local checkouts (app.update_check.is_dev_version — setuptools_scm .devN+g<sha> builds or the +dev fallback) always get { dev: true }, since there's no PyPI release an arbitrary in-between commit corresponds to. Otherwise returns { dev: false, update_available: bool, current_version, latest_version?, platform?, upgrade_command?: str \| null, download_url?: str \| null, releases_page_url?, releases?: [{ version, name, body, url, published_at }] } — upgrade_command (vibe-seller upgrade) on macOS/Linux/WSL vs. download_url (GitHub releases page) on native Windows, matching the two install paths in the README. releases lists GitHub release notes for every version newer than the installed one (capped at 5), used for the frontend's "what's new" popup shown once per login (see useUpdateCheck / UpdateAvailableModal).

screenshots.py — Screenshot Serving

Method Path Description
GET /api/screenshots/{screenshot_id} Serve screenshot PNG file

attachments.py — File Attachments

Method Path Description
POST /api/attachments/{task_id} Upload attachment. 400 on a disallowed type, 413 past the shared cap — see Upload limits
GET /api/attachments/{task_id} List attachments
GET /api/attachments/file/{attachment_id} Download attachment

Upload limits

Every multipart upload route shares one contract, defined in app/uploads.py: the same MIME allow-list (PNG / JPEG / GIF / WebP / PDF) and one ceiling, MAX_UPLOAD_SIZE (100MB). It previously lived in four places — create-task attachments at 10MB, chat staging at 15MB, the vision reference image at 15MB, and a hardcoded copy in the browser — so the same file was accepted by one box and refused by the next.

Routes covered: POST /api/attachments/{task_id}, POST /api/tasks/{task_id}/staged, and POST /api/tasks/{task_id}/image/upload-reference.

Size is enforced while streaming, in two layers:

Layer What it bounds Response
UploadBodyLimitMiddleware (ASGI, app/uploads.py) The request body itself, counted on the receive channel — so a chunked upload with no Content-Length cannot spool an unbounded temp file before the route runs 413
save_upload() Memory while writing; aborts mid-write and deletes the partial file 413

The middleware applies only to multipart/form-data — it is not a global request-body cap, so a large JSON PUT is unaffected. It is registered before CORSMiddleware so CORS stays outermost and a 413 reaches a cross-origin caller as a readable status rather than an opaque network error.

The frontend never states the limit itself: max_upload_size from GET /api/settings drives both the hint text and the "too large" message (frontend/src/uploadLimits.ts).

channels.py — Message Channels

Method Path Description
GET /api/channels/types List channel types
POST /api/channels/configure Configure channel
GET /api/channels/active List active channels
POST /api/channels/{id}/poll Poll for messages + extract events
POST /api/channels/{id}/send Send message (read-write channels)
DELETE /api/channels/{id} Remove channel

cron.py — Cron Jobs

Method Path Description
GET /api/cron/jobs List cron jobs
POST /api/cron/jobs Create cron job
DELETE /api/cron/jobs/{id} Delete cron job
POST /api/cron/jobs/{id}/pause Pause job
POST /api/cron/jobs/{id}/resume Resume job

ziniao_accounts.py — Ziniao Accounts

Method Path Description
GET /api/ziniao-accounts List accounts
POST /api/ziniao-accounts Create account
PUT /api/ziniao-accounts/{id} Update account
DELETE /api/ziniao-accounts/{id} Delete account
GET /api/ziniao-accounts/{id}/browsers List browser profiles (returns structured status JSON on error)
POST /api/ziniao-accounts/{id}/restart Kill + relaunch Ziniao in WebDriver mode (Mac only, requires running_normal state)

ads.py — Amazon Ads API service

A store can be bound to the hosted ads service; vibe-seller holds no Amazon credential. Nothing is configured: the service address is a constant in app/ads_client.py, and the installation registers itself with the service (POST /installations) the first time the stores are synced. The key it gets is stored encrypted and never returned. Amazon stores only.

Method Path Description
GET /api/ads/config Whether this installation has registered (configured); never the key
POST /api/ads/stores/sync Register if needed, upload the stores and record which are authorized; write an authorized store's marketplaces back onto its platforms / countries / platform_countries (add only). Also how a deployment learns a consent finished; pulls the skill once a store is authorized
POST /api/ads/stores/{id}/unbind Revoke one store's authorization (admin only). Body {"purge": bool}: also delete its ad history, unless another installation still manages the same Amazon ads account (kept_shared). Removes the skill when no store is left authorized
GET /api/ads/stores/{id}/auth-url A consent link for the store (admin only)
POST /api/ads/call The agent's one door (MCP tool vibe_seller_ads_call); the key is added here, never seen by the agent

/api/ads/call:

  • Task scoping. task_id is required. A task bound to a store reaches that store only; only a store-less task may name any authorized store.
  • Reserved paths. /me, /auth-url, /assignments, /ads-binding, /installations belong to a person, not an agent, and are refused.
  • File answers. A list the service answers as CSV is saved to tasks/<id>/ads-data/; the agent gets the path, the row count and the metadata from the X-Meta header.

vision.py — Vision (AI image generation)

Method Endpoint Description
GET /api/vision/config kie.ai key status (masked), available models
PUT /api/vision/config Set the kie.ai key (admin only)
POST /api/tasks/{id}/image/generate MCP-tool entry; confirm-gated generation
POST /api/tasks/{id}/image/confirm Resolve a pending image request (confirm/edit/cancel)
GET /api/tasks/{id}/image/pending Recover a still-pending confirm card (for clients that connect after image_request fired)

See vision.md for the confirm-gate flow and SSE events (image_request, image_generated).

main.py — App-level Endpoints

Method Path Description
GET /api/health Liveness probe ({"status": "ok"})
GET /api/plugins List active plugins' frontend bundles for the dashboard loader: [{js_extension_path, requires_early_init}]. Returns [] in an OSS-only install (no frontend plugins). See backend.md § Plugin Framework.