Bredgio API · v0
Programmatic control plane for Bredgio agents. Use it to create agents, dispatch posts and comments, schedule recurring runs, and observe execution in realtime.
| Field | Value |
|---|---|
| Base URL | https://bredgio.letmesee.cc |
| WebSocket | wss://bredgio.letmesee.cc/api/social-agents/ws |
| Format | JSON (UTF-8) |
| Auth | PAT — Authorization: Bearer bk_pat_... |
| Stability | Pre-release — shapes may change |
Architecture in 30 seconds
┌──────────────┐ HTTP ┌──────────────┐ Mongo ┌──────────┐
│ Dashboard │ ─────────────► │ Bredgio API │ ──────────► │ MongoDB │
│ (Svelte UI) │ ◄───────────── │ (SvelteKit) │ └──────────┘
└──────────────┘ └──────┬───────┘
│ enqueue task
▼
┌──────────────┐ wss ┌──────────────┐
│ WS Hub │ ◄────────► │ Extension │
│ (Node + ws) │ commands │ (Chrome MV3) │
└──────────────┘ └──────────────┘
The dashboard talks to the HTTP API. The HTTP API persists tasks to MongoDB.
The WebSocket hub claims pending tasks, expands them into a sequence of
atomic commands (navigate, click, type, …) and ships them to the
agent's Chrome extension over a single long-lived socket. Results stream
back the same way and are merged into the task log.
What you can do with the API
The API exposes every dashboard resource as a programmable endpoint — so you can drop Bredgio into any workflow, any scheduling system, any AI assistant. Common uses:
- Manage agents programmatically — list, create, reconfigure, deactivate, rotate tokens.
- Dispatch posts on demand — pass a string of content, the API drops a task to the named agent's browser and returns the live post URL when it lands.
- Build recurring schedules — one-off, daily, weekly, cron. A built-in worker fires schedules at their
nextRunAtso you don't run your own cron. - Track execution in realtime — poll
GET /api/tasks/:idto see each step's log entry; on failure you get a structurederrorCodetelling you whether it was an FB login issue, a missing button, or a timeout. - Pull analytics —
GET /api/logsreturns the outreach event stream (sent / failed / skipped) with daily roll-ups, ready for BI tools. - Hand the wheel to AI — the official MCP server lets Claude, Cursor, or Cline dispatch tasks for you in natural language, no code required.
What you do not need: the login endpoint.
/api/auth/*exists for the dashboard web UI — external callers only need a Personal Access Token (PAT) and never have to touch cookies, sessions, or passwords.
Quick start
You'll need a PAT, an agent, and a browser with the extension installed. Five minutes, end to end.
1 · Mint a PAT in the dashboard (one-time setup)
Open the dashboard →
Settings → API Keys → + New PAT. Copy the generated bk_pat_... and
stash it in a password manager. The full token is shown only once.
PAT issuance requires a Pro plan or above. See Plan gate.
2 · Create an agent
curl -X POST https://bredgio.letmesee.cc/api/agents \
-H 'Authorization: Bearer bk_pat_...' \
-H 'Content-Type: application/json' \
-d '{"name":"Marketing seat #1","platforms":["facebook"]}'
The response includes agent.token — a long hex string the extension
uses to authenticate (different from your PAT). It's returned in full only
on creation and on explicit reveal / regenerate. Save it now.
3 · Install the extension on the operator's machine
Open the Chrome Web Store listing and click Add to Chrome. After install, click the Bredgio toolbar icon, paste the agent token from step 2, and hit Save & Connect — the agent comes online within a few seconds.
Environments that can't reach the Chrome Web Store (intranets, blocked by enterprise policy) can sideload instead: download
https://bredgio.letmesee.cc/bredgio-extension.zip?v=<version>, unzip, openchrome://extensions, enable Developer mode, and Load unpacked.
4 · Dispatch a post
curl -X POST \
https://bredgio.letmesee.cc/api/agents/<agentId>/command \
-H 'Authorization: Bearer bk_pat_...' \
-H 'Content-Type: application/json' \
-d '{
"action": "post",
"platform": "facebook",
"params": { "content": "Hello, world!\nFrom Bredgio." }
}'
You'll get back task.taskId. Poll GET /api/tasks/<taskId> to watch
status transition through pending → dispatched → running → completed,
or open https://bredgio.letmesee.cc/dashboard/tasks/<taskId> in the
dashboard to follow the live log.
Authentication
External callers always use PAT — one token, every endpoint, no login flow needed. Session cookies and agent tokens are used by Bredgio's own components (dashboard UI, Chrome extension) and don't come up in typical API usage.
| Caller | Scheme | Where it goes |
|---|---|---|
| Your code / automation / MCP server / CI | Personal Access Token (PAT) | Authorization: Bearer bk_pat_… — paid plans only |
| Dashboard web UI (internal) | Session cookie | Set by POST /api/auth/login. External callers should ignore this endpoint. |
| Chrome extension (internal) | Agent token | WebSocket ?token=… or heartbeat body. |
Plan gate — PAT issuance is paid
Programmatic access via PAT is only available on the Pro plan and
above. Free-plan accounts can use the dashboard normally but the
Settings → API Keys → New PAT button is disabled, and the
bredgio-mcp server will refuse to start without a PAT.
| Plan | Dashboard | Extension (agent token) | PAT issuance | MCP server |
|---|---|---|---|---|
| Free | ✅ | ✅ | ❌ | ❌ |
| Pro | ✅ | ✅ | ✅ | ✅ |
| Team / Enterprise | ✅ | ✅ | ✅ (multi-seat) | ✅ |
Attempting to mint a PAT on a Free plan returns:
{
"success": false,
"error": "Upgrade to Pro to enable API access",
"errorCode": "plan_upgrade_required",
"upgradeUrl": "https://bredgio.letmesee.cc/dashboard/billing"
}
Existing PATs continue working through the end of the current billing
period if you downgrade — after that they auto-revoke and return 401 plan_downgraded on every call.
Personal Access Token (PAT) — primary for external use
PATs look like bk_pat_3dd2104b9144794e76ff437f655784bb. Every endpoint
in this document accepts a PAT via the Authorization: Bearer … header
— same authority as the user has in the dashboard.
- Mint — Dashboard → Settings → API Keys → + New PAT. The full token is shown once, at creation.
- Revoke — Same panel, Revoke button. Effective immediately; subsequent calls return
401 invalid_key. - Scope — Account-wide. Can do anything the user can do in the dashboard except mint or revoke other PATs (UI-only).
- Plan gate — Requires Pro or higher (see table above).
Treat PATs like passwords: store them in a secrets manager, never commit to source control, rotate on suspected leak.
Agent token — Chrome extension only
Each agent has its own token (server-minted via
randomBytes(32).toString('hex'), 64 hex chars) which the extension
uses to authenticate against the WebSocket hub. This is not the token
your code should send to the API — your code uses a PAT; agent tokens
live inside the extension popup.
- Reveal — Reveal button on the dashboard agent page, or
GET /api/agents/:id/tokenvia the API. - Regenerate — Regenerate button (requires the account password); also
POST /api/agents/:id/tokenwith the password. The previous token is invalidated immediately and any extension session using it disconnects.
Session cookie — dashboard web UI only
The cookie is a signed JWT containing { userId, email, name, plan }.
It is HttpOnly, Secure, SameSite=Lax, and set by
POST /api/auth/login. It only exists to support the dashboard's
own web UI — external code should use a PAT and never call /api/auth/*.
Endpoints
Endpoint groups, in the order you'd usually need them.
Auth
POST /api/auth/login
Exchange email + password for a session cookie.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
email |
string | yes | Case-insensitive. |
password |
string | yes |
Response — 200 OK
{
"success": true,
"user": {
"id": "65fc...",
"email": "[email protected]",
"name": "Lucien",
"plan": "free"
}
}
Sets Set-Cookie: bredgio_session=<jwt>; HttpOnly; Secure; SameSite=Lax.
Errors — 400 (missing fields), 401 (invalid credentials), 500.
POST /api/auth/logout
Clears the session cookie. Always returns { "success": true }.
PATCH /api/auth/profile
Update mutable fields on the currently logged-in user. Re-issues the session cookie so the new claims are reflected.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | no | Trimmed. |
Response — 200 OK — same shape as /login.
Agents
An agent is a (human operator, browser profile) pair on a partner's
machine. Each agent has its own token, status (online / offline /
paused), and per-day usage counters.
GET /api/agents
List every agent owned by the authenticated user, including a
todayCount of tasks dispatched in the last 24h.
Response — 200 OK
{
"success": true,
"agents": [
{
"agentId": "65fc...",
"name": "Marketing seat #1",
"userId": "65f0...",
"userEmail": "[email protected]",
"status": "online",
"platforms": ["facebook"],
"config": { "dailyOutreachLimit": 5, "messageDelaySec": 30, "postDelaySec": 60 },
"lastSeenAt": "2026-05-18T03:14:15.000Z",
"extensionVersion": "0.2.1",
"todayCount": 7
}
]
}
POST /api/agents
Create a new agent and mint its token.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | Free-form label. |
platforms |
string[] | no | Subset of facebook, twitter, instagram, linkedin, threads. Defaults to []. |
Response — 200 OK
{
"success": true,
"agent": {
"agentId": "65fc...",
"name": "Marketing seat #1",
"platforms": ["facebook"],
"token": "7c4d9f8e3b21…",
"...": "other fields as in /api/agents"
}
}
token is only present on creation. Save it now.
GET /api/agents/:id
Detailed view, including the 20 most recent tasks and outreach log entries.
Response
{
"success": true,
"agent": { /* same as list */ },
"recentTasks": [ /* Task objects, see below */ ],
"recentLogs": [ /* OutreachLog objects */ ],
"todayCount": 7
}
PATCH /api/agents/:id
Update mutable fields. Pass only the keys you want to change.
Body (all optional)
| Field | Type | Notes |
|---|---|---|
name |
string | |
platforms |
string[] | Replaces the array. |
status |
online · offline · paused |
Manual override; usually you let the heartbeat manage this. |
config.dailyOutreachLimit |
number | |
config.messageDelaySec |
number | |
config.postDelaySec |
number |
Response — 200 OK — { success: true, agent }.
DELETE /api/agents/:id
Soft-delete the agent. Returns { success: true }.
GET /api/agents/:id/token
Reveal the current agent token. Owner-only.
{ "success": true, "token": "7c4d9f8e3b21…" }
POST /api/agents/:id/token
Rotate the agent token. Requires the user's password in the body — this is a sensitive operation that invalidates every running extension session on that agent.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
password |
string | yes | The authenticated user's account password. |
Response — 200 OK
{ "success": true, "token": "new-token-here…" }
Errors — 400 passwordRequired, 401 Incorrect password, 403 Forbidden.
POST /api/agents/:id/command
Dispatch a task to the agent. This is the main work-creating endpoint.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
action |
enum | yes | See Task actions. |
platform |
enum | yes | One of facebook, twitter, instagram, linkedin, threads. facebook and twitter (covers x.com) are shipped end-to-end today; the rest are planned. |
params |
object | yes | Action-specific. See per-action shapes below. |
scheduledFor |
ISO 8601 | no | Future timestamp. If omitted, the task is claimed immediately. Past timestamps are rejected with 400. |
Response — 200 OK
{
"success": true,
"task": {
"taskId": "65fc...",
"action": "post",
"status": "pending",
"dispatchedAt": "2026-05-18T03:14:15.000Z",
"scheduledFor": null
}
}
Task actions
| Value | Platform | Status | params shape |
|---|---|---|---|
post |
facebook, twitter | shipped | { "content": "string (newlines preserved)", "media": [{ "url": "…", "type": "image/jpeg" }] } |
post-to-groups |
shipped | { "topic": "…", "content": "…", "maxGroups": 5, "intervalSec": 300 } |
|
comment |
— | planned | { "postUrl": "…", "content": "…" } |
comment-feed |
— | planned | { "feedUrl": "…", "content": "…" } |
fetch-posts |
— | planned | { "feedUrl": "…", "limit": 10 } |
search |
— | planned | { "query": "…" } |
smart-post |
— | planned | { "topic": "…", "tone": "…" } |
smart-comment |
— | planned | { "postUrl": "…", "stance": "…" } |
friend-msg |
— | planned | { "userId": "…", "content": "…" } |
send-msg |
— | planned | { "threadId": "…", "content": "…" } |
group-outreach |
— | planned | { "groupUrl": "…", "template": "…" } |
auto-outreach |
— | planned | { "policyId": "…" } |
The validator accepts every action above today. Three flows are
registered server-side: facebook + post, facebook + post-to-groups,
and twitter + post (covers x.com). Other combinations dispatch
successfully but fail at execution time with code: action_not_implemented.
Attaching images: params.media
media is an optional array on the post action. Each entry is { url, type }:
url— a URL that's publicly fetchable. The simplest path is to callPOST /api/media/uploadfirst and pass the returnedurlback unchanged; it points at Bredgio's own/api/media-proxy/<key>route which the agent's Chrome extension can fetch directly (host_permissions already cover it). External URLs work too, but only if they require no auth and don't block the extension with CORS / referer restrictions.type— MIME. Supported:image/jpeg,image/png,image/webp,image/gif.
Array order = Facebook attachment order. Empty or omitted = text-only post. Video and reels are not supported.
curl -X POST \
https://bredgio.letmesee.cc/api/agents/<agentId>/command \
-H 'Authorization: Bearer bk_pat_...' \
-H 'Content-Type: application/json' \
-d '{
"action": "post",
"platform": "facebook",
"params": {
"content": "Today’s new menu 🍣",
"media": [
{ "url": "/api/media-proxy/bredgio/usr_…/abc.jpg", "type": "image/jpeg" },
{ "url": "/api/media-proxy/bredgio/usr_…/def.jpg", "type": "image/jpeg" }
]
}
}'
POST /api/media/upload
Upload an image to Bredgio's R2 storage. Upload once, then re-use the
returned url across as many params.media entries as you need.
Body — multipart/form-data
| Field | Type | Required | Notes |
|---|---|---|---|
file |
file | yes | JPG / PNG / WebP / GIF, 10 MB max per file. |
Response — 200 OK
{
"success": true,
"url": "/api/media-proxy/bredgio/usr_…/1748000000000-a1b2.jpg",
"directUrl": "https://img.casinoarticle.top/bredgio/usr_…/1748000000000-a1b2.jpg",
"key": "bredgio/usr_…/1748000000000-a1b2.jpg",
"type": "image/jpeg",
"size": 482103
}
url— the proxy URL. This is what you pass back intoparams.media[].url. Relative path, served from Bredgio's own origin.directUrl— the R2 CDN URL. Useful for frontends that want fast thumbnails; external API callers can ignore it.key— internal R2 key, reserved for a future delete endpoint.
Errors
| Status | error |
|---|---|
400 |
file is required / Empty file / Unsupported type: … |
401 |
Not authenticated — missing or invalid token |
413 |
File too large (max 10MB) |
500 |
Storage not configured — server .env missing R2 variables |
curl -X POST \
https://bredgio.letmesee.cc/api/media/upload \
-H 'Authorization: Bearer bk_pat_...' \
-F file=@./post-image.jpg
Tasks
A task is one unit of work. It starts at pending, gets claimed by the
hub (dispatched), executes step by step (running), and ends in
completed, failed, or cancelled.
GET /api/tasks
List tasks owned by the caller.
Query
| Param | Type | Default | Notes |
|---|---|---|---|
agentId |
string | — | Filter to one agent. |
status |
enum | — | pending, dispatched, running, completed, failed, cancelled. |
limit |
number | 50 | Max 200. |
Response — 200 OK
{
"success": true,
"tasks": [
{
"taskId": "65fc...",
"agentId": "65f0...",
"action": "post",
"platform": "facebook",
"status": "completed",
"dispatchedAt": "2026-05-18T03:14:15.000Z",
"startedAt": "2026-05-18T03:14:16.123Z",
"completedAt": "2026-05-18T03:14:32.001Z",
"scheduledFor": null,
"currentStep": null,
"errorCode": null,
"result": { "post": { "url": "https://www.facebook.com/.../posts/123" } }
}
]
}
GET /api/tasks/:id
Single-task detail including the full step log.
Response — 200 OK
{
"success": true,
"task": {
"taskId": "65fc...",
"...": "all fields from the list endpoint",
"params": { "content": "Hello, world!" },
"log": [
{ "ts": "...", "step": "navigate", "status": "ok" },
{ "ts": "...", "step": "click_compose", "status": "ok" },
{ "ts": "...", "step": "type_content", "status": "ok" },
{ "ts": "...", "step": "submit", "status": "ok" },
{ "ts": "...", "step": "extract_post_url","status": "ok", "data": { "url": "..." } }
]
}
}
Errors — 403 Forbidden if the caller does not own the task.
Task log entries
| Field | Type | Notes |
|---|---|---|
ts |
ISO 8601 | When the entry was appended. |
step |
string | Slug of the current step (e.g. click_compose, submit). |
status |
info · ok · warn · error |
|
message |
string | Free text — usually empty on ok. |
data |
object | Step-specific payload, e.g. the captured url for extract_post_url. |
errorCode |
string | Present when status === 'error'. See Error codes. |
Schedules
A scheduled post materialises into one or more dispatched tasks
according to a frequency rule. Schedules are stored separately from
tasks; a background worker (running in the WS hub process) wakes every
few seconds, finds due schedules, and calls the same dispatch path as
/api/agents/:id/command.
GET /api/schedules
Query
| Param | Type | Default | Notes |
|---|---|---|---|
agentId |
string | — | |
status |
enum | — | active, paused, completed, cancelled. |
limit |
number | 100 |
Response
{
"success": true,
"schedules": [
{
"scheduleId": "65fc...",
"agentId": "65f0...",
"platform": "facebook",
"content": "Good morning, world.",
"frequency": "daily",
"runAt": null,
"timeMinutesUTC": 540,
"dayOfWeek": null,
"cronExpr": null,
"timezone": "Asia/Taipei",
"status": "active",
"nextRunAt": "2026-05-19T09:00:00.000Z",
"lastRunAt": "2026-05-18T09:00:00.000Z",
"createdAt": "2026-05-10T12:00:00.000Z"
}
]
}
POST /api/schedules
Create a schedule.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
agentId |
string | yes | Must be owned by the caller. |
platform |
enum | yes | See platform enum. |
content |
string | yes | The post body. Newlines preserved. |
frequency |
oneoff · daily · weekly · cron |
yes | See Schedule semantics. |
runAt |
ISO 8601 | conditional | Required when frequency=oneoff. |
timeMinutesUTC |
number 0–1439 |
conditional | Required when frequency=daily or weekly. |
dayOfWeek |
number 0–6 |
conditional | Required when frequency=weekly. 0=Sunday. |
cronExpr |
string | conditional | Required when frequency=cron. 5-field cron. |
timezone |
IANA string | no | Informational; server normalises to UTC. |
Response — 200 OK
{ "success": true, "schedule": { /* full doc */ } }
PATCH /api/schedules/:id
Edit any subset of content, status, or the frequency tuple
(frequency + runAt/timeMinutesUTC/dayOfWeek/cronExpr). If you
touch any timing field, the server re-validates the full frequency
spec and recomputes nextRunAt — so always send a coherent timing
group, not a half-update.
Setting status to anything other than active clears nextRunAt.
Response — { success: true, schedule }.
DELETE /api/schedules/:id
Hard delete. { success: true } on success.
Schedule semantics
| Frequency | When it fires | Required fields |
|---|---|---|
oneoff |
Once, at runAt. Auto-transitions to completed after firing. |
runAt (future) |
daily |
Every day at timeMinutesUTC minutes past UTC midnight. |
timeMinutesUTC |
weekly |
Every week on dayOfWeek at timeMinutesUTC. |
timeMinutesUTC, dayOfWeek |
cron |
Whenever the 5-field cron matches (UTC). | cronExpr |
cron schedules are intentionally excluded from the dashboard's month
calendar view because their cadence isn't naturally per-day.
Logs
OutreachLog is a denormalised, append-only event stream — separate
from tasks — that captures every message-sending attempt with its
outcome (sent / failed / skipped). It powers the /dashboard/logs
page and is the right place to look for analytics, not the task log.
GET /api/logs
Query
| Param | Type | Default | Notes |
|---|---|---|---|
agentId |
string | — | |
platform |
string | — | |
result |
sent · failed · skipped |
— | |
days |
number | 7 | Lookback window. |
limit |
number | 100 |
Response
{
"success": true,
"logs": [
{
"logId": "65fc...",
"agentId": "65f0...",
"platform": "facebook",
"result": "sent",
"targetUrl": "https://facebook.com/...",
"targetName": "Casino Royale Group",
"messageSent": "...",
"errorMessage": null,
"timestamp": "2026-05-18T03:14:15.000Z"
}
],
"summary": {
"total": 42,
"sent": 38,
"failed": 3,
"skipped": 1,
"byDay": [ { "day": "2026-05-18", "sent": 5, "failed": 0, "skipped": 0 } ]
}
}
Extension endpoints
These are called by the Chrome extension running on operator machines, not by your code.
POST /api/extension/heartbeat
Used during the install handshake (the popup pings this once with the pasted token to mark the agent online before the WS connection establishes). Steady-state heartbeating happens over WebSocket pings.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
token |
string | yes | Agent token. |
version |
string | no | Extension version (e.g. "0.2.1"). |
Response — 200 OK
{
"success": true,
"agent": {
"agentId": "65fc...",
"name": "Marketing seat #1",
"userEmail": "[email protected]",
"status": "online",
"platforms": ["facebook"]
}
}
Errors — 400 (no token), 401 Invalid token.
Locale
POST /api/locale
Persist a UI-language preference for the dashboard. Pure UI concern; you shouldn't need it from automation.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
locale |
en · zh-TW |
yes |
Response — { success: true, locale }. Sets the locale cookie.
WebSocket hub protocol
The hub is the realtime backbone between Bredgio and every extension instance. One connection per agent, multiplexed for every command issued to that agent.
Connect
wss://bredgio.letmesee.cc/api/social-agents/ws?token=<agent_token>&version=<ext_ver>
token(required) — the agent token fromPOST /api/agentsorGET /api/agents/:id/token. Missing or invalid token →HTTP/1.1 401before the upgrade.version(optional) — extension version, stored on the agent doc asextensionVersion.
The upgrade itself runs over Caddy on the public hostname and is routed
internally to a Node process on port 3032.
Frame format
Every frame is a JSON object with a type discriminator.
Server → client
type |
Shape | Meaning |
|---|---|---|
command |
{ type: 'command', id: uuid, cmd: string, args: object } |
Execute the named atomic command. |
ping |
{ type: 'ping' } |
Keepalive every 30 s. Reply with pong. |
Client → server
type |
Shape | Meaning |
|---|---|---|
hello |
{ type: 'hello', version?: string } |
Optional. Sent right after connect. |
pong |
{ type: 'pong' } |
Reply to a server ping. Sockets idle > 90 s are closed. |
result |
{ type: 'result', id: uuid, ok: boolean, data?: any, error?: string } |
Reply to a command, correlated by id. |
Atomic commands
The server-side flow planner expands a task (e.g. facebook + post)
into a sequence of these. The extension MUST NOT invent its own flows —
new product behaviour ships server-side.
cmd |
args |
Returns (data) |
|---|---|---|
navigate |
{ url: string, waitForLoad?: boolean } |
{} |
click |
{ selectors: string[], timeoutMs?: number } |
{ matched: string } (the selector that hit) |
type |
{ selector: string, text: string } |
{} |
wait |
{ seconds?: number, selector?: string, timeoutMs?: number } |
{} |
evaluate |
{ expression: string, awaitPromise?: boolean } |
{ result: any } |
screenshot |
{ format?: 'png' | 'jpeg', quality?: number } |
{ dataUri: string } |
cookies |
{ urls?: string[] } |
{ cookies: Cookie[] } |
selectors for click is an ordered fallback list — the extension
tries each until one matches. This is how flows survive Facebook's
locale-dependent DOM (English "Post" vs Traditional Chinese "貼文").
Per-command timeout defaults: click/type 10 s, navigate 30 s, the
hub also enforces a 60 s end-to-end timeout per command and rejects the
correlated promise on disconnect.
Lifecycle
extension hub db
│ │ │
├─── connect ──► │
│ (auth via ?token=) │
│ ◄────── upgrade 101 │
│ ◄────── ping (every 30s) │
├─── pong ────► │
│ │
│ ◄── claimNextPendingTask ─┤
│ ◄────── command#1 (navigate) │
├─── result#1 ─► │
│ ──── appendTaskLog ──────►│
│ ◄────── command#2 (click) │
├─── result#2 ─► │
│ │
│ ─── finishTask / failTask ►
│ ◄────── disconnect 1008 if token revoked
Stale agent sweep
Every 60 s the hub runs a sweep that flips agents to offline if their
lastSeenAt is older than 120 s. This is the safety net for extensions
that crash, get uninstalled, or have their machine put to sleep without
a clean close frame.
Error envelope
Every error response is:
{
"success": false,
"error": "human-readable message"
}
with a meaningful HTTP status code. Some responses include extra hints
(e.g. passwordRequired: true from the token regenerate endpoint when
the password was omitted).
Error codes
These appear as errorCode on failed tasks and inside task log entries.
| Code | Meaning | Typical fix |
|---|---|---|
fb_login_required |
The operator is not logged in to Facebook in the browser the extension controls. | Have the operator log in, then redispatch. |
fb_compose_not_found |
Couldn't find the "Create post" entry point. | Usually Facebook A/B/C-testing the home page — wait or report it. |
fb_textbox_not_found |
Compose dialog opened but no editable region appeared. | Same as above. |
fb_post_button_not_found |
Composed the post but couldn't locate the submit control. | Same as above. |
fb_unknown |
Catch-all for unmapped Facebook errors. | Inspect log for the underlying step + message. |
action_not_implemented |
The dispatched action has no server-side flow yet. | See Task actions. |
agent_offline |
No extension was connected when the task was claimed. | Bring the agent online and redispatch. |
command_timeout |
A single atomic command exceeded its timeout. | Network or page slowness — retry. |
disconnected |
The extension dropped during execution. | Retry. |
plan_upgrade_required |
The caller is on Free and tried to mint a PAT or hit a Pro-only endpoint. | Upgrade at upgradeUrl returned in the error body. |
plan_downgraded |
A previously valid PAT was auto-revoked when the account downgraded. | Resubscribe or re-mint after upgrading. |
invalid_key |
The Authorization: Bearer token is unknown, revoked, or malformed. |
Mint a new PAT and rotate. |
HTTP status reference
| Status | When |
|---|---|
400 |
Validation failed — missing field, bad enum, malformed JSON, past scheduledFor. |
401 |
Missing or invalid session cookie / agent token / PAT / user password. |
403 |
Authenticated but unauthorised — e.g. accessing another user's agent. |
404 |
The agent / task / schedule does not exist. |
500 |
Unhandled server error. Safe to retry idempotent reads. |
Code examples
Every example uses a PAT. Set BREDGIO_API_KEY to your bk_pat_....
Node.js — dispatch and poll
const base = 'https://bredgio.letmesee.cc';
const headers = {
authorization: `Bearer ${process.env.BREDGIO_API_KEY}`,
'content-type': 'application/json'
};
const dispatch = await fetch(`${base}/api/agents/${agentId}/command`, {
method: 'POST',
headers,
body: JSON.stringify({
action: 'post',
platform: 'facebook',
params: { content: 'Hello from Node\nLine two.' }
})
}).then((r) => r.json());
const { taskId } = dispatch.task;
for (;;) {
const task = await fetch(`${base}/api/tasks/${taskId}`, { headers })
.then((r) => r.json())
.then((r) => r.task);
if (['completed', 'failed', 'cancelled'].includes(task.status)) {
console.log(task.status, task.result ?? task.errorCode);
break;
}
await new Promise((res) => setTimeout(res, 2000));
}
Python — schedule a daily post
import os, requests
headers = {'Authorization': f"Bearer {os.environ['BREDGIO_API_KEY']}"}
r = requests.post(
'https://bredgio.letmesee.cc/api/schedules',
headers=headers,
json={
'agentId': 'YOUR_AGENT_ID',
'platform': 'facebook',
'content': 'Good morning ☀️',
'frequency': 'daily',
'timeMinutesUTC': 0, # 00:00 UTC
'timezone': 'Asia/Taipei'
}
)
r.raise_for_status()
print('Scheduled:', r.json()['schedule']['scheduleId'])
cURL — list today's tasks
curl -H "Authorization: Bearer $BREDGIO_API_KEY" \
'https://bredgio.letmesee.cc/api/tasks?limit=100&status=completed' \
| jq '.tasks | length'
Support
Open an issue at the project repository or message the maintainer directly if you need API help, a new action type, or a bug investigated.