REST API
BackTalk has a public REST API for reading and triaging signals, listing your keywords and watchers, pulling the report, and triggering a sync. It's a server-to-server surface: authenticate with a workspace API key as a bearer token. There are no cookies and no CORS headers — call it from your backend, a script, or a cron job, not a browser.
Base URL: https://backtalk.sh/api/v1
Authentication
Create a key in Settings → API keys (admin or owner only). The token is shown once, at creation — store it somewhere safe. Send it on every request:
curl https://backtalk.sh/api/v1/me \
-H "Authorization: Bearer btk_YOUR_KEY"
A key is tied to one workspace. It stops working the moment it's revoked, expires, or the member who created it leaves the workspace. Tokens are stored only as a SHA-256 hash; BackTalk can't show you a key again or recover a lost one — revoke it and make a new one.
Every response sends Cache-Control: no-store.
Scopes
Each key carries one or both scopes:
| Scope | Grants |
|---|---|
read | GET signals, a single signal, keywords, watchers, and the report. |
write | Everything in read, plus triaging a signal (PATCH) and triggering a sync (POST /sync). |
A request that needs a scope the key doesn't have returns 403 forbidden. There is no config-write scope: keywords, watchers and webhooks are managed in the app, not over the API.
Endpoints
GET /me
Introspect the key — which workspace and scopes it carries. Useful for debugging.
curl https://backtalk.sh/api/v1/me -H "Authorization: Bearer btk_YOUR_KEY"
{
"workspace": { "id": "…", "name": "Acme's workspace" },
"key": { "id": "…", "name": "CI pipeline", "prefix": "btk_1a2b3c4d", "scopes": ["read"] }
}
GET /signals
List signals, newest first, with inbox-style filters. Requires read.
| Query param | Values |
|---|---|
status | unread, all (default), starred, replied, ignored |
watcher | a watcher id, or none for keywords in no watcher |
source | comma-separated source ids, e.g. hackernews,github |
sentiment | comma-separated: positive, neutral, negative, unknown |
since / until | ISO date (2026-07-15) or datetime — bounds on when the signal was found |
limit | 1–100 (default 50) |
cursor | opaque keyset cursor (see Pagination) |
curl -G https://backtalk.sh/api/v1/signals \
-H "Authorization: Bearer btk_YOUR_KEY" \
--data-urlencode "sentiment=negative" \
--data-urlencode "since=2026-07-15" \
--data-urlencode "limit=20"
{
"signals": [
{
"id": "…",
"source_id": "hackernews",
"title": "…",
"snippet": "…",
"url": "https://news.ycombinator.com/item?id=…",
"state": "new",
"starred": false,
"sentiment": "negative",
"found_at": "2026-07-15T09:12:00Z"
}
],
"next_cursor": "eyJmb3VuZF9hdCI6…"
}
GET /signals/:id
Full detail for one signal. Requires read. A signal id from another workspace returns 404 not_found — never another tenant's data.
PATCH /signals/:id
Triage a signal. Requires write. Body (JSON, at least one of state / starred):
{ "state": "ignored", "ignore_reason_chip": "job_ad", "ignore_reason_text": "recruiter post" }
state:new,seen,replied, orignored. (spamis an AI classification, not a triage action.)starred:true/false.ignore_reason_chip:not_relevant,job_ad,different_product,spam— only meaningful whenstateisignored.ignore_reason_text: free text (≤ 500 chars).
curl -X PATCH https://backtalk.sh/api/v1/signals/SIGNAL_ID \
-H "Authorization: Bearer btk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"starred": true}'
Returns the updated signal.
GET /keywords and GET /watchers
List the workspace's keywords (phrase, variants, exclude terms, active state) and watchers. Both require read.
GET /report?period=today|7d|30d
The /app/report rollup for a rolling window: volume, replied count, oldest-unread age, sentiment (positive/neutral/negative and net = positive − negative), per-source counts, and a zero-filled trend. Requires read.
curl "https://backtalk.sh/api/v1/report?period=today" -H "Authorization: Bearer btk_YOUR_KEY"
POST /sync
Trigger a manual sync for the workspace. Requires write. This reuses the in-app "Sync now" gate: a workspace-wide cooldown and a per-plan daily cap. When you hit either, you get 429 with a Retry-After header.
curl -X POST https://backtalk.sh/api/v1/sync -H "Authorization: Bearer btk_YOUR_KEY"
Pagination
GET /signals uses keyset (cursor) pagination, ordered by found_at then id, descending. When a page is full there's a next_cursor; pass it back as ?cursor=… for the next page. When next_cursor is null, you've reached the end. Cursors are opaque — don't construct or parse them.
Errors
Every non-2xx response is the same envelope:
{ "error": { "code": "forbidden", "message": "This API key needs the \"write\" scope." } }
| Code | HTTP | When |
|---|---|---|
unauthorized | 401 | Missing/invalid/revoked/expired key, or its creator left the workspace. Sends WWW-Authenticate: Bearer. |
forbidden | 403 | The key lacks the scope the endpoint needs. |
workspace_read_only | 403 | The workspace is read-only (lapsed trial / inactive subscription). Reads still work; writes are blocked. |
not_found | 404 | No such resource in this workspace. |
invalid_request | 400 | Failed validation, malformed JSON, or an oversized body. |
rate_limited | 429 | Too many requests (or sync cooldown/cap). Sends Retry-After. |
internal | 500 | Something failed on our end. The message is generic; details are in our logs. |
Branch on code, not on the message text.
Rate limits
Each key is limited to 120 requests per minute (fixed window). Every response includes:
X-RateLimit-Limit— the per-minute ceiling.X-RateLimit-Remaining— requests left in the current window.X-RateLimit-Reset— Unix seconds when the window resets.
Over the limit returns 429 rate_limited with Retry-After. Limits are per key, so give each integration its own key.
Repeated failed authentication attempts are also throttled (per client IP). If you're getting 429 on a key you believe is valid, check the Authorization header format first: Bearer btk_….