The WatchDog Bot API
A complete reference for every programmatic interface WatchDog Bot exposes — the in-process Python SDK (import wd) that bots use at runtime, the cloud REST endpoints powering features like AI Fix and the newsletter, and the roadmap for a future bot-control REST API.
In this document
Two APIs: SDK vs cloud REST
WatchDog Bot has two distinct programmatic surfaces, and they serve different purposes. Knowing which one to call (or whether to call any of them) is the first step.
| API | What it's for | Where it runs |
|---|---|---|
| wd Python SDK | Code your bot uses at runtime: logging, credentials, state, demo flag | Inside your bot process (in-memory) |
| Cloud REST | Account-level features: AI Fix, newsletter signup, audit telemetry | watchdogbot.cloud HTTPS endpoints |
Most users only ever interact with the wd SDK — they import it in their bot and use a few of its helpers. The cloud REST endpoints exist for the desktop app and the website backend to call; they're documented here for transparency, not because most users will call them directly.
The wd SDK — in-process Python API
The SDK is bundled with WatchDog Bot and auto-injected into your bot's sys.path. No pip install required. Source: ~120 lines of pure stdlib Python; the platform's resources/sdk/wd/ directory.
Complete reference at /docs/python-sdk. Quick overview:
import wd
# Structured logging — appears in the dashboard with level filters
wd.log.info("Bot started")
wd.log.warning("rate limit at %d%%", 87)
wd.log.error("API failed: %s", err)
# Encrypted credential access by name
conn = wd.connection("Kalshi")
print(conn.api_key, conn.api_secret, conn.base_url)
# Persistent per-bot state directory
state_file = wd.bot_dir() / "state.json"
# Runtime introspection
wd.bot_id() # → uuid string
wd.bot_name() # → "Kalshi NYC Weather"
wd.is_demo() # → True/False
Stability: The SDK follows semantic versioning. Currently at v0.1.0. Breaking changes will only happen on major version bumps. New helpers (wd.bars, @wd.every, wd.state) are planned for v1.0.
Cloud REST endpoints (current)
All endpoints are hosted at https://watchdogbot.cloud and require HTTPS. Responses are JSON. Error responses include an error field with a human-readable message.
Public endpoints (no auth required)
{ ok: true }.// Request
POST /api/newsletter
Content-Type: application/json
{
"email": "you@example.com",
"source": "homepage" // optional — for analytics
}
// Response (success)
HTTP/1.1 200 OK
{ "ok": true }
// Response (validation error)
HTTP/1.1 400 Bad Request
{ "ok": false, "error": "Invalid email address" }
Authenticated endpoints (require Supabase JWT)
The website's authenticated endpoints expect a Bearer token from your active Supabase session. Supply it via the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
If the token is missing, malformed, or expired, the endpoint returns 401 Unauthorized.
// Request
POST /api/ai/fix
Authorization: Bearer <your-supabase-jwt>
Content-Type: application/json
{
"bot_id": "7f3a-c8b1-…", // optional but recommended
"bot_code": "...full Python source...",
"error_logs": ["TypeError: ...", "..."], // array of log strings
"language": "python" // optional, defaults to "python"
}
// Response (success)
HTTP/1.1 200 OK
{
"ok": true,
"fix": "...proposed code diff or full replacement...",
"explanation": "The market object is None when…"
}
// Response (subscription gate)
HTTP/1.1 402 Payment Required
{ "ok": false, "error": "AI Fix requires an active subscription...", "status": "inactive" }
// Response (rate limit)
HTTP/1.1 429 Too Many Requests
{ "ok": false, "error": "50 calls per 24 hours per user.", "retry_after_hours": 24 }
Internal endpoints (admin only)
The /api/admin/* namespace requires the caller to be an admin (verified via the is_app_admin column in profiles). These are not intended for third-party use and are gated by both JWT and the admin flag.
Authentication
The website uses Supabase Auth (email + password, optional OAuth providers). When a user signs in via the dashboard, supabase-js stores a session in browser localStorage; subsequent fetches include the JWT in the Authorization header.
For programmatic clients (e.g., scripts running against the API for testing):
import requests, os
from supabase import create_client
sb = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"])
res = sb.auth.sign_in_with_password({
"email": os.environ["WD_EMAIL"],
"password": os.environ["WD_PASSWORD"],
})
jwt = res.session.access_token
# Now call authenticated endpoints
r = requests.post(
"https://watchdogbot.cloud/api/ai/fix",
headers={"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"},
json={"bot_code": "...", "error_logs": ["..."]},
)
print(r.status_code, r.json())
Security: Never embed your account password or service-role key in client-side code. The JWT is the only credential safe to use from the browser, and it expires within hours — supabase-js handles refresh automatically.
Rate limits
| Endpoint | Limit | Key |
|---|---|---|
| POST /api/newsletter | 10 / 10 min | IP address |
| POST /api/ai/fix | 50 / 24 hours | User ID |
| POST /api/submissions | 20 / day | User ID |
| GET /api/* | 240 / minute | IP address |
The in-memory rate limiter is best-effort and resets on Railway redeploys (typically less than once a day). For most use cases this is irrelevant; if you build something that hits limits, contact us about an enterprise plan.
Error handling
| Status | Meaning | What to do |
|---|---|---|
| 400 | Validation error — bad body / missing field | Check the error message; fix the input |
| 401 | Missing or invalid JWT | Re-authenticate; supabase-js usually handles this for you |
| 402 | Subscription required (AI Fix) | Show user the upgrade flow; check trial status |
| 403 | Forbidden — you don't own that resource | Check ownership; never retry with admin escalation |
| 404 | Resource not found | Check the URL / ID; not retryable |
| 413 | Payload too large (AI Fix: code > 200KB) | Trim the request body |
| 429 | Rate limit exceeded | Wait the indicated period and retry |
| 502 | Upstream LLM unavailable (AI Fix) | Retry once after 30s; if persistent, contact support |
| 503 | Service misconfigured (missing env var) | Contact support — not retryable from client |
All non-2xx responses include a JSON body with an error field describing the issue:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{
"ok": false,
"error": "50 calls per 24 hours per user. Try again tomorrow.",
"retry_after_hours": 24
}
Roadmap — control API
Today, the only way to control bots programmatically is by editing your code and using the WatchDog Bot desktop app to start/stop them. We're considering a public REST control API as a future feature, gated behind a Pro or Enterprise tier.
If we ship it, the surface would look like this:
?since= and ?level= filters.If a programmatic bot-control API would unblock something you're building, tell us. We prioritize the roadmap based on demand we can measure.
WatchDog Bot