Pressfront API
Every account, every post, who started it and who carried it, as data your AI can explain. Alerts land in Slack or Discord too.
Make a key in SettingsComes with Trace, $149 a month.Of 99 accounts, 66 were critical and 9 supportive. It started with your 10:53 PM post. The jump came at 2:36 AM, when @quietlantern quoted it: 57 accounts arrived in the next hour, against 3 the hour before.
Three groups of about 10 posted within 5 minutes of each other. That is a timing match, not proof anyone coordinated. A model flagged 2 posts as a possible threat; want me to pull them?
In Settings, press Make a key. It starts with pf_live_ and we show it once, so copy it then. Keep it secret like a password. Tick Read only for a key that can look but never change anything.
Claude (claude.ai or the app): Settings, Connectors, Add custom connector, paste https://pressfront.co/api/mcp, then Connect. Sign in to Pressfront and press Allow. No key needed: it reads only, unless you tick the box to let it start traces too.
Claude Code, one line:
claude mcp add --transport http pressfront https://pressfront.co/api/mcp \
--header "Authorization: Bearer pf_live_YOUR_KEY"ChatGPT: make a GPT, open Actions, Import from URL: https://pressfront.co/api/v1/openapi.json. Authentication: API key, Bearer, paste your key.
Any AI that can make calls (Claude Code, Cursor, agents): make a key in Settings. It shows the one line to paste, with your key already in it.
Claude Desktop, Cursor and others: the set-ups are under Connect any AI below.
Then just ask: “what happened with my last trace?”, “who carried it?”, “show me the posts flagged as threats”, “has @someone come at me before?”
No key yet? Read 3 real traces right now with the demo key. It only reads, never starts anything:
01M6S180Z4AYZ47ACQT09S4WEX01M6S483JT47MPY51RV3XTQ6BDGET /instant/spacex.01M6PX06C05X1TV8V1NYZNTKJ3Who started the story, in four stops:
curl https://pressfront.co/api/v1/traces/01M6S180Z4AYZ47ACQT09S4WEX/case \
-H "Authorization: Bearer pf_live_DemoKeyForPressfrontTraceSample1"GET /samples lists all three with their links. The demo key also reads one invented trace, 01JDEM0SAMP1E000000000000T.
With your own key, the whole situation as plain facts, ready to paste into any AI:
curl https://pressfront.co/api/v1/traces/TRACE_ID/brief \
-H "Authorization: Bearer pf_live_YOUR_KEY"Every critical post, biggest accounts first:
curl "https://pressfront.co/api/v1/traces/TRACE_ID/posts?side=critical&sort=followers" \ -H "Authorization: Bearer pf_live_YOUR_KEY"
In Settings, paste a Slack or Discord webhook link (or any https address of yours) and press Send a test. Every alert we email you lands there too: “@yourname: 42 posts in the last 15 min. Usually about 4.”
Slack: Incoming Webhooks, pick a channel, copy the link. Discord: channel settings, Integrations, Webhooks, Copy Webhook URL.
gives your AI the whole story: the counts, a quote from each side, how it started, who carried it, who moved together. Our numbers and their words; no AI of ours writes it, so nothing is made up and nobody is labeled.
Base: https://pressfront.co/api/v1. Every call needs Authorization: Bearer pf_live_.... Answers are JSON (the brief is Markdown). The grey word is the same thing as an AI tool. Machine-readable: openapi.json.
| Path | What it does | |
|---|---|---|
| Samples | ||
| GET | /samples | 3 real finished traces any key reads, the demo key included: a story going around, a very big name, an instant check. Each with links to its /case and /brief. list_samples |
| Account | ||
| GET | /me | Who this key belongs to. whoami |
| A situation, in plain words | ||
| POST | /understandtext, files | Say back what we would look into: the person (an @handle) or the story going around, and 2 or 3 plain lines. Read by a language model. Stores nothing, starts nothing. Up to 5 screenshots or PDFs as files: [{mediaType, data}] (base64, REST only). 300 a day. understand_situation |
| Traces | ||
| GET | /traceslimit, status, before | Your traces, newest first. list_traces |
| POST | /traces | Start a trace of a name. start_trace |
| POST | /tracestext, claim, files | Start a trace of a situation or a story going around: {"claim": "Lara went partying after the concert was cancelled"}, or what is going on in plain words as {"text": "..."} (screenshots or PDFs in files, REST only). A story is traced back to who said it first. The answer carries understood. trace_situation |
| GET | /traces/:idwait | One trace: status and headline numbers. get_trace |
| Read a trace | ||
| GET | /traces/:id/briefformat, partial | The whole situation as plain facts, for an AI to explain. Start here. get_trace_brief |
| GET | /traces/:id/case | The answer on top of the trace page, as data: one headline and four stops, Started, Spread, Who, Before? Who said it first, who spread it, whether they showed a photo or link, whether it happened before. Name and story traces. get_case |
| GET | /traces/:id/storypartial | What happened, as data. The What happened tab: headline, counts by side and tone, the biggest voice on each side, how it started, the jump, who carried it, claims, concentration, pace, and every measured fact. get_trace_story |
| GET | /traces/:id/accountsside, sort, min_followers, new, q, full, partial, limit, cursor | Every account in the trace. Each account with its side, followers, account age, when it first showed up, how it interacted, who it came through, and its first post. list_trace_accounts |
| GET | /traces/:id/accounts/:handle | One account in full. Everything on one account in this trace: profile, side, every post of theirs we hold with each post's own reading. get_trace_account |
| GET | /traces/:id/postsside, sort, since, until, q, account, threat, partial, limit, cursor | Every post we hold, in order. Each post with its account, time, link, text, and its own side and tone. list_trace_posts |
| GET | /traces/:id/timeline | Arrivals over time. Posts (or accounts) per time bin with the critical share, the busiest stretch, stretches we did not read, and activity by hour and weekday. get_trace_timeline |
| GET | /traces/:id/groups | Who moved together. Accounts that posted within minutes of each other, posted the same words, or look alike (created together, same hours), and accounts that posted at the subject before. get_trace_groups |
| GET | /traces/:id/who | Who they are. Where X says the accounts are based, what they call themselves in their bios, renamed accounts, the typical account, big and verified accounts, journalists by their own bio, accounts the subject follows, and what ChatGPT and other AIs answer about the subject. get_trace_who |
| GET | /traces/:id/sites | Other sites. Posts about it on other sites (Reddit, forums, archives and more), each with its link, plus which sites we checked and the domains the posts linked to. get_trace_sites |
| GET | /traces/:id/evidence | Saved posts, threats, personal info. The saved copies of posts (text as it stood when the trace ran), posts a model flagged as a possible threat, posts carrying the subject's personal info, and the block list. get_trace_evidence |
| GET | /traces/:id/evidence/pack | The evidence pack. The sealed zip a full name trace builds a few minutes after it finishes: one capture record per post, screenshots, Internet Archive copies, a SHA-256 manifest and an RFC 3161 time-stamp. status none, building, ready or failed; when ready, a one-hour download link. get_evidence_pack |
| Accounts | ||
| GET | /accounts/:handlelimit | Has this account shown up in my traces before? lookup_account |
| Alerts | ||
| GET | /alertssince, before, limit | Alerts we sent you, newest first. list_alerts |
| GET | /names | The names we watch for you. list_watched_names |
| POST | /names | Watch one more name. watch_name |
| DEL | /names/:id | Stop watching a name. unwatch_name |
A new trace takes a few minutes (POST /traces answers with etaSec). GET /traces/:id?wait=25 holds the answer until it finishes or 25 seconds pass, so repeat it instead of polling; the trace.done webhook works too. While it runs, /traces/:id shows soFar (accounts, critical, stage), and ?partial=true on /brief, /story, /accounts and /posts reads the first results, usually within about two minutes. They are for name traces; a story trace has its answer on /traces/:id/case and its brief on /traces/:id/brief.
| side= | Means | |
|---|---|---|
strongly_critical | Critical, and insulting or hostile. | |
mocking | Critical, by joking or ridicule. | |
disagreeing | Critical, with no insult or joke. | |
critical | All three together. | |
supportive, neutral, not_read | The rest. |
Sides are read by a language model from each post's text, and every answer says so. A reading, not a finding: read the posts before acting on one.
Lists come in pages: pass the next value back as cursor (or before on /traces and /alerts) until next is null. Each post carries source: post (read on its own), saved (a saved copy, read with its account; traces from before Oct 24, 2026) or link (only the link was kept).
The MCP address is https://pressfront.co/api/mcp (streamable HTTP). It offers every endpoint as a tool, and tells the AI to quote posts rather than label people. Two ways in:
/.well-known/oauth-authorization-server. A connected app shows in Settings as a connected app; Disconnect there cuts it off at once. Read only unless you tick the box on the Allow screen.Authorization: Bearer pf_live_...: everything else, below.Claude Desktop: Settings, Developer, Edit Config, then add:
{ "mcpServers": { "pressfront": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://pressfront.co/api/mcp", "--header", "Authorization:${PF_KEY}"],
"env": { "PF_KEY": "Bearer pf_live_YOUR_KEY" }
} } }Cursor: ~/.cursor/mcp.json
{ "mcpServers": { "pressfront": {
"url": "https://pressfront.co/api/mcp",
"headers": { "Authorization": "Bearer pf_live_YOUR_KEY" }
} } }ChatGPT (a custom GPT): Actions, Import from URL, https://pressfront.co/api/v1/openapi.json. Authentication: API key, Auth type Bearer. Privacy policy: https://pressfront.co/privacy.html.
Anything else: generate a client from the OpenAPI file, or call the REST endpoints directly.
For your own https link we POST JSON with a Pressfront-Event header and a Pressfront-Signature header. Slack and Discord links get one plain line instead.
{
"id": "evt_lq3k9x2a7f1c9e",
"type": "alert.spike",
"created_at": "2026-11-05T21:14:00.000Z",
"data": {
"name": "@yourname", "platform": "x",
"count": 42, "usual": 4, "window_min": 15,
"level": 1,
"link": "https://pressfront.co/account.html?open=01J...#trace"
}
}| type | When | |
|---|---|---|
alert.spike | Posts at a watched name jump to 3 times its usual 15 minutes (at least 12). level 2, 3... means it doubled again since the last alert. | |
alert.sweep | The 6 hour check of a name or phrase found it moving. | |
alert.watch | A trace you Watch pinged: a big account replied, quoted or mentioned you, or the pace jumped. data.reason says which. | |
trace.done | One of your traces finished (data.status is done, thin or failed; data.started_via is api or site). Sent to your own server only, never to Slack or Discord, so a channel is not filled with every trace you run. | |
test | You pressed Send a test. |
We wait 8 seconds for a 2xx. If it fails we try twice more, after 1 and 5 minutes, with the same id, so you can skip one you already handled. If 3 alerts in a row never land, we pause the link and email you; email alerts keep coming. Fix it and press Send a test in Settings: a test that lands turns it back on.
The signature header is t=<unix time>,v1=<hex>, where the hex is HMAC-SHA256 of t + "." + raw body with your signing secret (Settings, under your link). Node:
const crypto = require("crypto"); function fromPressfront(rawBody, header, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const want = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; return fresh && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1 || "")); }
60 calls a minute and 5,000 a day per key, 5 keys per account. An AI's tool call counts as one call; connecting and listing tools is free. Reading traces costs nothing extra. Starting them: 20 a day through the API, and a Trace plan's first 300 traces each month are full; after that they run as lighter traces until the month resets (GET /me shows how many full ones are left). Every answer carries X-RateLimit-Remaining. Over the limit you get a 429 with Retry-After in seconds.
| Status | error | Means |
|---|---|---|
| 401 | missing_key, bad_key | No key sent, or it was deleted. |
| 402 | no_plan, names_full | The account is not on Trace any more, or all 10 names are in use. |
| 403 | read_only | A read-only key tried to change something. |
| 400 | bad_side, bad_cursor, bad_date | A filter or page value we do not take. The message says which. |
| 404 | not_found | No such endpoint, name, trace or account on your account. |
| 409 | not_ready, failed | The trace is still running, or failed. Wait for trace.done. |
| 422 | not_a_name_trace | A topic trace: only its headline numbers are on /traces/:id. |
| 429 | rate_limited, traces_today | Slow down. Wait Retry-After seconds. |
| 500 | server | Our side. Try again; if it keeps happening, email support@pressfront.co. |
Every error is {"error": "code", "message": "plain words"}.
The full wording is in our Terms (section 7) and Privacy Policy (what we log and for how long).
More names, more calls, or something your team needs wired in: support@pressfront.co. Tell us what you are building.