# Weir documentation > Public Reddit and X data for developers and AI agents, over REST and MCP. Each platform returns its own fields under its own names. Paid per returned result, from prepaid credits. Every page of the documentation in one file, in reading order: guides first, then one page per API operation, each with its price. Relative links point at pages of the documentation site. --- # Weir Public Reddit and X data for developers and AI agents, over a REST API and a remote MCP server. Each platform returns its own fields, under its own names. You pay per result returned, from prepaid credits, and a call that fails costs nothing. --- # Quickstart A working search in five minutes. No card. Using an AI agent? You may not need a key at all: [connect it with MCP](/mcp). ## 1. Get a key Create one under [API keys](https://weirapp.io/dashboard/keys). It is shown once. ```bash export WEIR_API_KEY="weir_live_..." ``` A key is a bearer credential: whoever holds it spends your credits. Keep it out of browser code and public repositories. ## 2. Make a request ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures","limit":10}' ``` Swap `reddit` for `x` to search X instead. ## 3. Read the response ```json { "data": [ { "id": "1t4k9x2", "subreddit": "saas", "title": "Our checkout has been failing since Tuesday", "author": "eng_at_acme", "score": 212, "numComments": 47, "createdAt": "2026-07-30T11:04:22.000Z" } ], "meta": { "requestId": "req_…", "nextCursor": "…" } } ``` `data` is a plain array of that platform's own shape. `meta.nextCursor` gets you the next page. ## 4. Check what it cost Every response carries the charge: ``` x-credits-charged: 31 x-credits-remaining: 4969 ``` Ten Reddit posts cost 31 credits: 1 for the call and 3 per post. Every [reference page](/reference/reddit/searchReddit) states its price, and a failed call costs nothing. ## Next - [Authentication](/authentication). Scope a key before it goes near production. - [Errors](/errors). `402` and `429` need opposite responses. - [Response shapes](/response-shapes). What each platform returns. --- # Authentication Two ways in. The REST API takes an API key. The [MCP server](/mcp) takes an API key too, or signs your client in with OAuth so you never handle a key. ## API keys One header, on every request: ``` Authorization: Bearer weir_live_... ``` Create keys under [API keys](https://weirapp.io/dashboard/keys). A key is shown once, when you create it. Only its hash is stored, so a lost key cannot be recovered. Create a new one. A key is a bearer credential: whoever holds it spends your credits. Keep it on a server, in an environment variable, and out of browser code and public repositories. Keys labelled test start with `weir_test_`. They work the same way and spend the same credits; the label only helps you tell keys apart. ## OAuth, for MCP clients Claude, ChatGPT, Cursor, and most coding agents connect to `https://api.weirapp.io/mcp` with OAuth 2.1. The client opens a browser, you sign in to Weir and approve, and the client keeps the token. It refreshes the token itself. Each approved connection appears under [API keys](https://weirapp.io/dashboard/keys) as ` (MCP)`. Revoke it there to disconnect that client. A connection can read Reddit and X and see your usage. It gets monitor and webhook access only if you grant it on the approval screen. Clients that cannot open a browser use an API key on the same URL instead. [MCP](/mcp) lists which client does which. ## `401` and `403` `401` means the key or token is missing, malformed, revoked, or expired. `403` means it is valid but lacks a scope. Different problems, different fixes. ## Scopes Grant the narrowest set that does the job. | Scope | Grants | | ---------------- | ---------------------------------------------------------- | | `read:reddit` | Reddit endpoints | | `read:x` | X endpoints | | `read:social` | Umbrella. Every platform, including ones added later | | `usage:read` | Usage introspection | | `monitors:read` | List and read monitors, runs, and results | | `monitors:write` | Create, change, delete, and trigger monitors. Implies read | | `webhooks:read` | List webhooks and deliveries | | `webhooks:write` | Create, change, delete, and test webhooks. Implies read | Implication is resolved server-side, so you never list both halves. `/v1/whoami`, `/v1/usage`, and `/v1/limits` answer any valid key, whatever its scopes, so a key can always find out why it is being refused. `read:social` is an umbrella rather than a synonym for "Reddit and X" on purpose: a key holding it keeps working when a new platform ships. A key holding `read:reddit` and `read:x` does not, because it said exactly which two it wanted. ## Expiry Set one. The dashboard defaults to 90 days. An expired key returns `401` the moment it lapses, with no grace period. "No expiration" exists because CI needs it, but a key that lives forever outlives the laptop it was pasted onto. ## Rotating No downtime, because keys are independent: 1. Create the new key with the same scopes. 2. Deploy it. 3. Confirm traffic moved. `/v1/whoami` reports which key id is in use, free. 4. Revoke the old one. Revocation is immediate. ## Checking a key ```bash curl --url https://api.weirapp.io/v1/whoami \ --header "Authorization: Bearer $WEIR_API_KEY" ``` It costs nothing, so it is always safe as a connectivity check. --- # Errors Every failure has the same shape: ```json { "error": { "code": "quota_exceeded", "message": "The account is out of credits.", "retryable": false, "details": { "field": "limit" } }, "meta": { "requestId": "req_01J…" } } ``` **Branch on `retryable`**, not on a status-code list you maintain yourself. `meta.requestId` is on every response and in the `x-request-id` header. Quote it in support requests. ## Status codes | Status | Code | Meaning | Retry? | | ------ | --------------------- | -------------------------------------------- | ----------------------------- | | `400` | `bad_request` | Malformed. `details` names the field. | No. Fix it. | | `401` | `unauthorized` | Key missing, malformed, revoked, or expired. | No. | | `402` | `quota_exceeded` | **Out of credits.** | **No.** See below. | | `403` | `insufficient_scope` | Key lacks a [scope](/authentication#scopes). | No. Issue a key with it. | | `404` | `not_found` | No such resource, or not public. | No. | | `409` | `conflict` | A limit would be exceeded, such as monitors. | No. Remove one first. | | `413` | `bad_request` | Body over the size limit. | No. | | `429` | `rate_limit_exceeded` | **Rate limited.** | **Yes**, after `Retry-After`. | | `500` | `internal_error` | Our fault. | Yes, with backoff. | | `503` | `source_unavailable` | A platform was unreachable. | Yes, with backoff. | | `504` | `timeout` | Outlived its deadline. | Yes, with backoff. | None of these costs credits. A refused or failed call is never charged. ## `402` and `429` are opposites Both mean "not right now". Treating them the same is the most expensive mistake against this API. **`429` is temporary.** You are going too fast. Wait for `Retry-After` and the same request works. **`402` is not.** The account is out of credits. Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. A retry loop on `402` runs until you stop it and never succeeds. An agent that hits it should stop and tell its user. `/v1/usage`, `/v1/limits`, and `/v1/whoami` stay reachable at `402`, so you can always find out why. ## Rate limits Every metered response carries the current state: ``` ratelimit-limit: 30 ratelimit-remaining: 27 ratelimit-reset: 1800000060 ``` | Header | Meaning | | --------------------- | --------------------------------------------------------- | | `ratelimit-limit` | Requests allowed per minute. | | `ratelimit-remaining` | Requests left in the current window. | | `ratelimit-reset` | When the current one-minute window ends, in Unix seconds. | | `Retry-After` | On a `429` only: seconds to wait before trying again. | The limit is a rolling minute, so budget frees up gradually rather than all at once at `ratelimit-reset`. Use `Retry-After` to decide when to try again. Endpoints are weighted by how much upstream work they do. A search counts as 2 requests and a comment tree as 4, while a single lookup counts as 1. Each operation's [reference page](/reference/reddit/searchReddit) states its weight. It is the work being limited, not the request count. The free tier allows 30 requests a minute. Paid tiers allow more. `/v1/limits` returns the exact figure for your account. ## When a platform is down You get `503` with `source_unavailable`, and nothing is charged. Each platform is its own endpoint, so one being down never affects the other. Fall back to the other platform, or retry with backoff. --- # Pagination Cursors, not page numbers. ```json { "meta": { "nextCursor": "eyJ2IjoxLCJz…" } } ``` Send it back as `cursor` to continue. **`nextCursor: null` is the only stop condition**. A page can come back empty with more results behind it. ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures","cursor":"eyJ2IjoxLCJz…"}' ``` Searches take `cursor` in the body. Endpoints read with `GET`, such as a user's posts or a post's replies, take it in the query string. ## Keep the query identical A cursor is bound to the exact request it came from. Change any filter and you get `400`, not a silent reset to page one. Quietly serving page two of a different search produces a plausible, wrong answer that nobody notices. Change the query, start a new cursor. ## Cursors are per platform A Reddit cursor means nothing to X. Walking both means walking two cursors, and finishing one does not stop the other. ## Every page is billed `limit` bounds one call. 100 Reddit posts cost 301 credits: 1 for the call and 3 per post. 100 X posts cost 101. ```ts async function* redditPages(query: string, maxPages: number) { let cursor: string | undefined; for (let page = 0; page < maxPages; page += 1) { const response = await fetch("https://api.weirapp.io/v1/reddit/search", { method: "POST", headers: { Authorization: `Bearer ${process.env.WEIR_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ query, limit: 100, ...(cursor ? { cursor } : {}) }), }); if (!response.ok) throw new Error(`Weir answered ${response.status}`); const { data, meta } = await response.json(); yield data; // this page's array of posts if (!meta.nextCursor) return; cursor = meta.nextCursor; } } for await (const posts of redditPages("payment failures", 20)) { for (const post of posts) console.log(post.url); } ``` Each page is an array of posts, so the inner loop reads it directly. **`maxPages` is not optional in spirit.** An unbounded walk over a broad query keeps finding results and keeps charging. A ceiling is the difference between a backfill and a bill. ## Do not build your own cursor It is a signed envelope, not an offset. The signature covers the position _and_ a fingerprint of the query, so a hand-edited cursor is rejected rather than honoured. To resume tomorrow, store the string exactly as given. --- # Response shapes Each platform returns its own fields, under its own names. Nothing is renamed, nothing is dropped to make the shapes match. ## A Reddit post ```json { "id": "1t4k9x2", "subreddit": "saas", "title": "Our checkout has been failing since Tuesday", "selftext": "We've had three customers report…", "author": "eng_at_acme", "permalink": "/r/saas/comments/1t4k9x2/…", "url": "https://www.reddit.com/r/saas/comments/1t4k9x2/…", "score": 212, "upvoteRatio": 0.94, "numComments": 47, "over18": false, "createdUtc": 1785582262, "createdAt": "2026-07-30T11:04:22.000Z" } ``` - `score` is upvotes minus downvotes. It can be negative, and Reddit fuzzes it. - `author` is a username string. A deleted author is the literal `"[deleted]"`. - `createdUtc` and `createdAt` are the same instant, in both formats. ## An X post ```json { "id": "1948372615540293", "text": "Three of our customers hit the same checkout failure today.", "url": "https://x.com/patio11/status/1948372615540293", "lang": "en", "conversationId": "1948372615540293", "inReplyToId": null, "author": { "id": "9412", "username": "patio11", "name": "Patrick McKenzie", "verified": true, "followersCount": 412903 }, "likeCount": 1876, "retweetCount": 233, "replyCount": 134, "viewCount": 91204, "createdAt": "2026-07-30T11:44:51.000Z" } ``` - `id` is a string because it exceeds 2^53. Parsing it as a number corrupts it. - Counts are nullable. `null` means X did not report it, not zero. ## Three things that will trip you up **`author` is a string on Reddit, an object on X.** `post.author` versus `post.author.username`. Do not write one accessor for both. **There is no shared engagement field.** A Reddit `score` and an X `likeCount` are different measurements. There is no exchange rate between an upvote and a like. **Only `id`, `url`, and `createdAt` are carried by both.** Everything else belongs to one platform. ## Searching both platforms Two calls, in parallel. Each succeeds or fails on its own. ```ts const search = (platform: "reddit" | "x") => fetch(`https://api.weirapp.io/v1/${platform}/search`, { method: "POST", headers: { Authorization: `Bearer ${process.env.WEIR_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ query: "checkout failures", limit: 25 }), }).then((response) => response.json()); const [reddit, x] = await Promise.allSettled([search("reddit"), search("x")]); ``` `allSettled` rather than `all`, so X being down does not throw away the Reddit results. There is no combined endpoint. Two unrelated shapes in one response is harder to use than two responses holding one each, and a combined call could not let X fail without taking Reddit with it. ## Sorting is per platform Each sorts against its own corpus in its own vocabulary. Reddit takes `relevance | hot | top | new | comments`, X takes `latest | top`. There is no cross-platform ranking, because there is no shared score to rank by. --- # Billing and credits Weir is prepaid. You hold a balance of credits, every successful call spends some, and there is no subscription and no invoice at the end of the month. When the balance runs out, calls stop: you can never be billed for more than you bought. ## What a call costs You pay for results returned, not requests made. | Result | Credits | | ---------------- | ------- | | X post | 1 | | X profile | 2 | | Reddit post | 3 | | Reddit comment | 3 | | Reddit community | 1 | A successful call also costs **a 1-credit minimum**, even when it returns nothing: reaching a platform is the expensive part whether or not it had an answer. Every operation publishes its price in an `x-weir-billing` extension, shown on its [reference page](/reference/reddit/searchReddit). The 3:1 ratio between Reddit and X is not a pricing lever. Reddit results cost us more to produce, and it is stated openly because the honest way to compare any two APIs is a rate per thousand results, which is how [pricing](https://weirapp.io/pricing) publishes it. ## What costs nothing - **Failed calls.** Anything `400` or above costs nothing, including `503`. - **Refused calls.** `402` and `429` never charge. - **The control plane.** Creating, listing, and triggering monitors and webhooks. - **Introspection.** `/v1/whoami`, `/v1/usage`, `/v1/limits`. Working out why you ran out of credit should not spend credit. ## Where credits come from - **Free grant.** 5,000 credits a month, no card needed. They arrive with your first call of the month, expire when the month ends, and do not roll over. One free grant per person: if you own several workspaces, your oldest one receives it. - **Credit packs.** Bought in the dashboard under **Billing** by a workspace owner or admin. **Purchased credits never expire.** | Pack | Credits | Per 1,000 X posts | Per 1,000 Reddit posts | | ------ | ---------- | ----------------- | ---------------------- | | $5 | 25,000 | $0.20 | $0.60 | | $20 | 120,000 | $0.167 | $0.50 | | $50 | 340,000 | $0.147 | $0.441 | | $200 | 1,600,000 | $0.125 | $0.375 | | $1,000 | 10,000,000 | $0.10 | $0.30 | Prices are in US dollars, before any sales tax or VAT, which is added at checkout. Card payments are processed by Dodo Payments, our merchant of record. You can also pay in USDC on Solana; see below. Spending always uses the credits that expire soonest first: this month's free grant before anything you bought. ### Paying with USDC Every pack can also be paid in **USDC on Solana**, at one USDC per dollar: the $20 pack costs 20 USDC. Under **Billing**, choose **Pay with USDC (Solana)** on a pack. The dashboard shows a [Solana Pay](https://docs.solanapay.com) request: - a **QR code** to scan with a mobile wallet (Phantom, Solflare, or any wallet that supports Solana Pay), and an **Open in wallet** link for a wallet on the same device; - the **amount**, the **recipient** address, and a **reference**, each with a copy button. Scanning or opening the request fills everything in. The transfer carries the request's reference, which is how Weir finds it on-chain and ties it to your purchase; nothing else about your wallet is needed. The page updates itself and shows the credits as soon as the transaction is **finalized** on Solana, usually within a minute of sending it. You can leave the page: the payment is still found, and your purchase history links back to the request. Things to know: - **Only USDC on Solana is accepted** (mint `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`). USDT, SOL, or USDC sent on another chain is not credited and may not be recoverable. - **Pay the request, not the address.** A transfer typed by hand without the reference cannot be matched automatically. If that happens, email billing@weirapp.io with the transaction signature. - **The request is shown as payable for 30 minutes**, and Weir keeps looking for its transfer for 24 hours, after which it is marked expired. A transfer that lands later is still credited once it is found: open the request again from your purchase history, or contact us. - **Paying more** than the price buys the pack; the excess is refunded on request. **Paying less** adds no credits: the payment is held for review, and we either let you top it up or refund it. - Your wallet pays the Solana network fee, a fraction of a cent. There is no sales tax line on a USDC payment and no Dodo invoice; the on-chain transaction is the receipt. ## Tiers Rate limits and how many monitors and webhooks a workspace may have rise with what it has bought in total, net of refunds. They never fall because you spent credits or because time passed. | Tier | Lifetime purchases | Requests/minute | Monitors | Webhooks | Hourly monitors | | ------ | ------------------ | --------------- | -------- | -------- | --------------- | | Free | $0 | 30 | 1 | 1 | No | | Tier 1 | $5 | 120 | 10 | 5 | Yes | | Tier 2 | $100 | 300 | 50 | 25 | Yes | | Tier 3 | $500 | 600 | 200 | 100 | Yes | `GET /v1/limits` returns your workspace's current tier and its limits. ## Reading the meter ``` x-credits-charged: 24 x-credits-remaining: 9976 ``` `x-credits-charged` is what that call cost. `x-credits-remaining` is what your workspace can still spend afterwards. Watch `x-credits-charged` after an unfamiliar call: because rates differ per platform, a query's price cannot be predicted from the request alone, so reading the header beats modelling it. `GET /v1/usage` returns the whole picture: `balance`, what is `reserved` by calls still in flight, what is `available`, and how much of this month's free grant is left and when it resets. ## Running out You get [`402`](/errors) with code `quota_exceeded`, and **it is not retryable**. Only a person can clear it, by adding credits under **Billing** in the dashboard. Retrying never will, however long you wait: a retry loop on a `402` runs forever. Two other refusals look similar and are not: - **`429` while credits are held.** When several calls run at once on a nearly empty balance, each holds enough to cover its worst case until it finishes. If the rest of your balance is held by calls still in flight, the next call gets `429` with `Retry-After`, and it succeeds once they finish. This one is safe to retry. - **`402` while spending is paused.** If a payment is refunded after its credits were spent, or a card payment is disputed, data calls return `402` with `details.reason: "billing_hold"` until it is resolved. Contact support@weirapp.io. ## Monitors A monitor run costs the 1-credit minimum plus the per-result rate for each **new** result it stores. Results it has already shown you are not charged again, so a run that finds nothing new costs 1 credit. There is no premium for scheduling. If a workspace runs out of credits, its monitors pause instead of failing on every run, and the dashboard shows why. They resume by themselves as soon as credits arrive: a purchase, or next month's free grant. ## Refunds An unused credit pack can be refunded within 30 days of purchase; once its credits are being spent it cannot. The free grant is never refundable. See [the refund policy](https://weirapp.io/refunds). Refunds of USDC payments are made by hand by support: email billing@weirapp.io from the workspace owner's address with the purchase date. The USDC is returned in full, on Solana, to the wallet that sent it, and the pack's credits are taken back when the refund is made. ## Controlling spend 1. **`limit` is per call.** Searching both platforms is two calls, so budget for both. 2. **Filter upstream.** You pay for what was returned, including what you then discard. 3. **Bound your pagination.** See [pagination](/pagination). --- # Monitors and webhooks A monitor watches something on a schedule and reports only what is new. ```bash curl --request POST \ --url https://api.weirapp.io/v1/monitors \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Checkout complaints", "target": { "kind": "search", "query": "checkout failing", "sources": ["reddit", "x"] }, "schedule": "daily" }' ``` ## What you can watch `target.kind` decides what a monitor polls. | Kind | Watches | Fields | | --- | --- | --- | | `search` | A phrase, across sources | `query`, `sources` | | `subreddit` | One community | `subreddit`, optional `query`, optional `includeNsfw` | | `subreddit_comments` | | `subreddit`, optional `query`, optional `includeNsfw` | | `reddit_user` | | `username`, optional `include`, optional `includeNsfw` | | `x_user` | One account's posts | `handle` | | `reddit_thread` | New comments on one Reddit post | `postId` | | `x_thread` | New replies to one X post | `postId` | Leave `query` off a `subreddit` target to be told about every new post in the community. ```json { "kind": "subreddit", "subreddit": "saas" } { "kind": "x_user", "handle": "naval" } { "kind": "reddit_thread", "postId": "https://www.reddit.com/r/saas/comments/1vdo8we/slug/" } ``` Identifiers are normalised, so paste whatever you have. A URL, an `r/` prefix, an `@`, and a `t3_` fullname all work. A thread watch returns comments rather than posts. Each result says which it is in `itemType`. An `x_user` monitor resolves the handle once and follows the account by its numeric id after that, so it keeps working if the account is renamed. | Schedule | Runs | | -------- | ----------------------------------------------- | | `manual` | Only when you call `POST /v1/monitors/{id}/run` | | `hourly` | Every hour. Paid tiers only | | `daily` | Every day (the default) | | `weekly` | Every week | How many monitors you can keep depends on your tier: one on the free tier, more on paid tiers. `GET /v1/limits` returns the number for your account. Going over returns `409`. ## "New" means new content, not a new id Results are deduplicated by a hash of the content, not by the platform's post id. That is the reason to use a monitor rather than a cron job around a search. An edited post keeps its id; a re-post gets a fresh one. Deduplicating by id alerts you twice for the second and never for the first. ## Reading results ```bash curl --url "https://api.weirapp.io/v1/monitors/$MONITOR_ID/results" \ --header "Authorization: Bearer $WEIR_API_KEY" ``` `/runs` gives the execution history, including failures and what they collected before failing. Both are free. So is triggering a run. You pay only for the results it collects, at the same rate as running the query by hand. A triggered run answers `202` with a job id and runs in the background, so read `/runs` for the outcome rather than expecting results in the response. ## Webhooks Be told instead of polling: ```bash curl --request POST \ --url https://api.weirapp.io/v1/webhooks \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Ops channel", "url": "https://hooks.example.com/weir", "events": ["monitor.result.created"] }' ``` The signing secret is returned **once**. Point a monitor at the webhook with its `webhookId`. | Event | Sent when | | ------------------------ | --------------------------------------------------------------- | | `monitor.result.created` | A run found something new. One delivery per run, up to 25 items | | `webhook.test` | You call `POST /v1/webhooks/{id}/test` | You can also subscribe to `monitor.run.failed`, but no deliveries are sent for it yet. Read a monitor's `/runs` to catch a run that failed. How many webhooks you can keep depends on your tier, the same way as monitors. ## What a delivery looks like A `POST` with a JSON body and three headers: ``` weir-signature: 5f2b… (hex HMAC-SHA256) weir-timestamp: 1800000000 weir-event-id: evt_… ``` ```json { "id": "evt_…", "type": "monitor.result.created", "createdAt": "2026-07-30T11:04:22.000Z", "data": { "monitorId": "…", "runId": "…", "kind": "search", "itemType": "post", "newResults": 3, "results": [] } } ``` `results` holds each platform's own record, exactly as the API returns it. ## Verifying a delivery The signature is `HMAC-SHA256(secret, "{timestamp}.{eventId}.{rawBody}")`, hex encoded, where `timestamp` is the `weir-timestamp` header, `eventId` the `weir-event-id` header, and `rawBody` the body exactly as received. All three parts are signed on purpose: the body alone could be replayed forever, the timestamp bounds that window, and the event id stops one valid signature being lifted onto another delivery inside it. Reject a timestamp more than five minutes from now, and compare in constant time. **Use the raw body.** Parsing and re-serializing JSON reorders keys, which changes the string and breaks the signature. This is the most common reason a correct secret still fails. Node, with Express: ```ts import { createHmac, timingSafeEqual } from "node:crypto"; import express from "express"; function verifyWeir(raw: Buffer, signature: string, timestamp: string, eventId: string): boolean { if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = createHmac("sha256", process.env.WEIR_WEBHOOK_SECRET!) .update(`${timestamp}.${eventId}.`) .update(raw) .digest("hex"); return ( signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) ); } const app = express(); app.post("/weir", express.raw({ type: "application/json" }), (request, response) => { const valid = verifyWeir( request.body, request.header("weir-signature") ?? "", request.header("weir-timestamp") ?? "", request.header("weir-event-id") ?? "", ); if (!valid) return response.status(400).end(); response.status(200).end(); const event = JSON.parse(request.body.toString("utf8")); console.log(event.id, event.type); // process it here, deduplicating on event.id }); ``` `express.raw`, not `express.json`, so the body arrives as the bytes that were signed. Python: ```python import hashlib import hmac import os import time def verify_weir(raw_body: bytes, signature: str, timestamp: str, event_id: str) -> bool: try: if abs(time.time() - int(timestamp)) > 300: return False except ValueError: return False message = f"{timestamp}.{event_id}.".encode() + raw_body secret = os.environ["WEIR_WEBHOOK_SECRET"].encode() expected = hmac.new(secret, message, hashlib.sha256).hexdigest() return hmac.compare_digest(expected.encode(), signature.encode()) ``` Pass the raw bytes: `request.get_data()` in Flask, `await request.body()` in Starlette and FastAPI. Go: ```go package webhooks import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "strconv" "time" ) // VerifyWeir reports whether a delivery was signed with the webhook's secret. func VerifyWeir(rawBody []byte, signature, timestamp, eventID, secret string) bool { issued, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return false } if age := time.Now().Unix() - issued; age > 300 || age < -300 { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(timestamp + "." + eventID + ".")) mac.Write(rawBody) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } ``` Read the body with `io.ReadAll(r.Body)` before decoding it. ## Delivery behaviour - **At least once.** A delivery can arrive more than once. `weir-event-id` stays the same when a delivery is retried, so deduplicate on it. - **Retries back off over about nine hours**: roughly 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours after each failure. Every attempt is recorded at `GET /v1/webhook-deliveries`. - **Respond within 10 seconds.** Return `2xx` first, then do the work. - **A `4xx` other than `429` is not retried.** It says the request itself was refused, and sending it again would be refused the same way. - **Redirects are not followed.** A `302` is a failure. A public URL redirecting to an internal one is the standard way to turn a webhook into request forgery. - **A persistently failing endpoint is disabled** after 20 failures in a row. One success clears the streak. `POST /v1/webhooks/{id}/test` sends a real signed delivery, free. --- # Set up with Skill Give your agent the ability to search public social data. One line, no integration code. ## 1. Install the skill Paste this into your agent's chat: ``` set up https://weirapp.io/SKILL.md ``` It fetches the manifest and configures itself: endpoints, response shapes, cost rules, and which errors are worth retrying. ## 2. Approve it, or give it a key If your agent runs in a client that speaks [MCP](/mcp), the manifest has it connect that way. You approve the connection in a browser and never handle a key. Otherwise it asks for a key. Create one under [API keys](https://weirapp.io/dashboard/keys). **It is shown once.** The manifest tells the agent to keep the key in an environment variable and never print it back. ## 3. Start using it Ask for something: - _What are people saying about our pricing on Reddit?_ - _Find complaints about our checkout from the last week._ - _Watch r/saas for mentions of our competitor and tell me when something new appears._ The agent picks the endpoint, runs it, and reads the cost off the response. --- Prefer a server connection over a manifest? See [MCP](/mcp). --- # Connect with MCP Weir runs a remote MCP server. Point any MCP client at this URL: ``` https://api.weirapp.io/mcp ``` There is nothing to install. The server speaks Streamable HTTP, so no client needs a local bridge. ## Two ways to authorize The client decides which one it uses. - **Sign in (OAuth).** Chat apps and most coding agents open a browser. You sign in to Weir, approve the connection, and the client keeps the token. You never copy a key. The connection appears under [API keys](https://weirapp.io/dashboard/keys) as ` (MCP)`. - **API key.** Anything that runs without a person at a browser sends a Weir key as `Authorization: Bearer weir_live_...`. That covers CI, `claude -p`, the Claude API and OpenAI API connectors, agent frameworks, and n8n. ## Pick your client | Client | Authorizes with | Setup | | --------------------------------- | --------------- | ----------------------------------------------------------- | | Claude (web, Desktop, mobile) | OAuth | [Claude and ChatGPT](/mcp-chat-apps#claude) | | ChatGPT | OAuth | [Claude and ChatGPT](/mcp-chat-apps#chatgpt) | | Claude Code | OAuth or key | [Coding agents](/mcp-coding-agents#claude-code) | | Codex | OAuth or key | [Coding agents](/mcp-coding-agents#codex) | | Cursor | OAuth | [Coding agents](/mcp-coding-agents#cursor) | | VS Code (GitHub Copilot) | OAuth or key | [Coding agents](/mcp-coding-agents#vs-code) | | Devin Desktop (formerly Windsurf) | OAuth or key | [Coding agents](/mcp-coding-agents#devin-desktop) | | Gemini CLI | OAuth or key | [Coding agents](/mcp-coding-agents#gemini-cli) | | Zed | OAuth or key | [Coding agents](/mcp-coding-agents#zed) | | Cline | OAuth or key | [Coding agents](/mcp-coding-agents#cline) | | Goose | OAuth or key | [Coding agents](/mcp-coding-agents#goose) | | Continue | Key | [Coding agents](/mcp-coding-agents#continue) | | Claude API (MCP connector) | Key | [APIs and frameworks](/mcp-frameworks#claude-api) | | OpenAI Responses API | Key | [APIs and frameworks](/mcp-frameworks#openai-responses-api) | | OpenAI Agents SDK | Key | [APIs and frameworks](/mcp-frameworks#openai-agents-sdk) | | LangChain | Key | [APIs and frameworks](/mcp-frameworks#langchain) | | Vercel AI SDK | Key | [APIs and frameworks](/mcp-frameworks#vercel-ai-sdk) | | n8n | Key or OAuth | [APIs and frameworks](/mcp-frameworks#n8n) | Claude and ChatGPT cannot send an API key. Cursor ignores one while the server offers sign-in, so it signs in too. ## Check that it works Ask your agent: ``` What's my Weir usage? ``` It should call `get_usage`, which is free, and answer with your credit balance. ## Tools Each tool is named after the API operation it calls, in snake_case: `searchReddit` is `search_reddit`. Every connection gets these: | Tool | Does | Credits | | --- | --- | --- | | `search_reddit` | Search public Reddit posts | 1 + 3 per Reddit post | | `get_reddit_post` | Get one public Reddit post | 1 + 3 per Reddit post | | `get_subreddit` | Get information about a public subreddit | 1 + 1 per subreddit | | `search_subreddit` | Search posts within one subreddit | 1 + 3 per Reddit post | | `get_reddit_comments` | Get a Reddit comment tree as a flat list, page by page | 1 + 3 per Reddit comment | | `get_subreddit_posts` | List a community's posts: hot, new, rising, top, or controversial | 1 + 3 per Reddit post | | `get_subreddit_comments` | Read a community's newest comments, across every post | 1 + 3 per Reddit comment | | `search_subreddits` | Find communities by name or topic | 1 + 1 per subreddit | | `get_reddit_user` | Get a Reddit account's public profile | 1 + 1 per Reddit user | | `get_reddit_user_posts` | List the posts an account has submitted | 1 + 3 per Reddit post | | `get_reddit_user_comments` | List the comments an account has made | 1 + 3 per Reddit comment | | `lookup_reddit` | Fetch up to 100 posts and comments, and 100 communities, in one call | 1 + 3 per Reddit post + 3 per Reddit comment + 1 per subreddit | | `search_x` | Search public X posts with composable filters | 1 + 1 per X post | | `get_x_posts` | Get up to 100 public X posts by id | 1 + 1 per X post | | `get_x_post` | Get one public X post | 1 + 1 per X post | | `get_x_replies` | Get replies to an X post | 1 + 1 per X post | | `get_x_thread` | Get the author's thread containing a post | 1 + 1 per X post | | `get_x_quotes` | Get posts quoting an X post | 1 + 1 per X post | | `get_x_retweeters` | Get accounts that reposted an X post | 1 + 1 per X user result | | `get_x_post_edits` | Get the edit history of an X post | 1 + 1 per X post | | `get_x_users` | Get up to 100 public X profiles by id | 1 + 2 per X profile | | `search_x_users` | Search X accounts | 1 + 1 per X user result | | `get_x_user` | Get a public X profile | 1 + 2 per X profile | | `get_x_user_about` | Get an X account's "About this account" details | 1 + 2 per X profile | | `get_x_user_posts` | Get a public X user timeline | 1 + 2 per X profile + 1 per X post | | `get_x_user_replies` | Get an X account's posts and replies | 1 + 2 per X profile + 1 per X post | | `get_x_user_media` | Get an X account's posts with media | 1 + 2 per X profile + 1 per X post | | `get_x_user_highlights` | Get the posts an X account highlights | 1 + 2 per X profile + 1 per X post | | `get_x_user_followers` | Get an X account's followers | 1 + 2 per X profile + 1 per X user result | | `get_x_user_following` | Get the accounts an X account follows | 1 + 2 per X profile + 1 per X user result | | `get_x_user_affiliates` | Get an organisation's affiliated X accounts | 1 + 2 per X profile + 1 per X user result | | `get_x_user_subscriptions` | Get the creators an X account subscribes to | 1 + 2 per X profile + 1 per X user result | | `get_x_list` | Get a public X List | 1 + 2 per X list | | `get_x_list_members` | Get the members of a public X List | 1 + 1 per X user result | | `get_x_list_posts` | Get the timeline of a public X List | 1 + 1 per X post | | `get_x_space` | Get an X Space | 1 + 2 per X space | | `get_usage` | Credit balance, holds, and this month's free grant | Free | | `get_limits` | Ceilings imposed by the account's tier | Free | | `get_identity` | Identify the account and scopes behind this key | Free | Credits are per call plus per result returned, the same as the REST API. A failed or refused call costs nothing. Each tool returns the same [response shapes](/response-shapes) as the endpoint behind it, and paged tools take the `cursor` from `meta.nextCursor`. These appear only on a connection that was granted monitor or webhook access when it was approved: | Tool | Does | Credits | | --- | --- | --- | | `list_monitors` | List monitors | Free | | `create_monitor` | Create a monitor | Free | | `get_monitor` | Get one monitor | Free | | `update_monitor` | Update a monitor | Free | | `delete_monitor` | Delete a monitor | Free | | `list_monitor_results` | List deduplicated results a monitor has found | Free | | `list_monitor_runs` | List a monitor's execution history | Free | | `run_monitor` | Queue a monitor run immediately | Free | | `list_webhooks` | List webhooks | Free | | `create_webhook` | Create a webhook | Free | | `update_webhook` | Update a webhook | Free | | `delete_webhook` | Delete a webhook | Free | | `test_webhook` | Queue a test delivery | Free | | `list_webhook_deliveries` | List recent delivery attempts | Free | They are separate because a monitor keeps running, and spending credits, after the agent stops. ## Revoking Every signed-in connection appears under [API keys](https://weirapp.io/dashboard/keys) as ` (MCP)`. Revoke it there and it stops working. A client that connected with an API key stops when you revoke that key. ## Troubleshooting - **Claude cannot reach the server.** Claude connects from Anthropic's cloud, not your machine, so the URL must be public. `claude_desktop_config.json` is for local servers and does not take this URL. - **The free Claude plan allows one custom connector.** Remove another to add Weir. - **Cursor ignores the key in `mcp.json`.** Expected. Remove the header and sign in. - **Cline will not connect.** Set `"type": "streamableHttp"`. Without it Cline uses the older SSE transport. - **n8n answers `405`.** Set the MCP Client Tool's server transport to HTTP Streamable. - **A tool returns `402`.** The account is out of credits. Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. ## What agents get wrong Worth putting in your system prompt: - **Start at `limit: 10`.** Widen once the results look right. - **Never retry a `402`.** Only a person can clear it, by adding credits. - **Filter in the query, not afterwards.** Every result returned is billed, including the ones you discard. - **Read the cost** of an unfamiliar call before looping on it. --- # Claude and ChatGPT Both add Weir as a connector and sign in with OAuth. Neither can send an API key, so there is no key to create. ## Claude Works in Claude on the web, Claude Desktop, and the mobile apps. The connector is added once and follows your account. 1. **Pro and Max:** open **Customize**, then **Connectors**, then **+ Add**, then **Add custom connector**. **Team and Enterprise:** open **Organization settings**, then **Connectors**, then **Add**, then **Custom**, then **Web**. On Team, only Owners can add one. 2. Name it `Weir` and paste the URL: ``` https://api.weirapp.io/mcp ``` 3. Leave the authentication settings as Claude detects them, and connect. Claude opens Weir's consent screen. Sign in and approve. **Check:** in a new chat, ask "What's my Weir usage?" Claude should call `get_usage`. Things to know: - Claude reaches the server from Anthropic's cloud, not from your machine. The URL has to be public, so a local API will not work here. - `claude_desktop_config.json` is for local servers. Do not put this URL in it. - The Free plan allows one custom connector. - If you sign in to Claude Code with the same Claude account, connectors added here show up there too. [Claude's guide to custom connectors](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) ## ChatGPT Custom MCP servers are available on ChatGPT Business and Enterprise/Edu, on the web. Pro accounts can connect one with read and fetch permissions. On Business, only admins and owners can add one. 1. Go to [chatgpt.com/plugins](https://chatgpt.com/plugins), click **+**, and choose **Add custom MCP server**. 2. Name it `Weir`, set the server URL, and choose **OAuth** for authentication: ``` https://api.weirapp.io/mcp ``` 3. Accept the warning, choose **Create as a plugin**, and install it. Sign in to Weir and approve when asked. 4. In a chat, mention `@Weir` to use it. You do not need to turn on developer mode first. **Check:** ask "@Weir what's my Weir usage?" ChatGPT should call `get_usage`. When Weir adds tools, open the plugin and use **Refresh**. A plugin published to a workspace keeps the tool list it was created with until an admin refreshes it. [OpenAI's guide to custom MCP servers](https://help.openai.com/en/articles/12584461) --- # Coding agents and IDEs Every client here speaks Streamable HTTP, so each one connects straight to the URL. Most sign in with OAuth on first use; the key alternative is for headless runs and CI. Keep a key in an environment variable, never in a file you commit. The check is the same everywhere: ask "What's my Weir usage?" The agent should call `get_usage`, which is free. ## Claude Code ```bash claude mcp add --transport http weir https://api.weirapp.io/mcp ``` Then run `/mcp` inside Claude Code and choose Weir to sign in. A browser opens; approve the connection. **With a key**, for CI, `claude -p`, and anything without a browser: ```bash claude mcp add --transport http weir https://api.weirapp.io/mcp --header "Authorization: Bearer $WEIR_API_KEY" ``` Your shell expands `$WEIR_API_KEY` before Claude Code sees it, so the key itself is saved in Claude Code's settings. To keep it out of every file, use a project `.mcp.json` instead. Claude Code expands `${WEIR_API_KEY}` when it connects: ```json { "mcpServers": { "weir": { "type": "http", "url": "https://api.weirapp.io/mcp", "headers": { "Authorization": "Bearer ${WEIR_API_KEY}" } } } } ``` The Claude Agent SDK cannot open a browser either, so it takes the same header. Pass these options to `query()`: ```ts const options = { mcpServers: { weir: { type: "http", url: "https://api.weirapp.io/mcp", headers: { Authorization: `Bearer ${process.env.WEIR_API_KEY}` }, }, }, allowedTools: ["mcp__weir__*"], }; ``` **Check:** `claude mcp list` shows `weir` as connected. Then ask "What's my Weir usage?" [Claude Code MCP docs](https://code.claude.com/docs/en/mcp) ## Codex One config serves the Codex CLI, the IDE extension, and the ChatGPT desktop app. In `~/.codex/config.toml`: ```toml [mcp_servers.weir] url = "https://api.weirapp.io/mcp" ``` Then sign in once: ```bash codex mcp login weir ``` **With a key**, name the environment variable that holds it instead: ```toml [mcp_servers.weir] url = "https://api.weirapp.io/mcp" bearer_token_env_var = "WEIR_API_KEY" ``` **Check:** `codex mcp list` shows `weir`. Then ask "What's my Weir usage?" [Codex MCP docs](https://learn.chatgpt.com/docs/extend/mcp) ## Cursor In `~/.cursor/mcp.json`, or `.cursor/mcp.json` for one project: ```json { "mcpServers": { "weir": { "url": "https://api.weirapp.io/mcp" } } } ``` Or [add it to Cursor](https://cursor.com/en/install-mcp?name=weir&config=eyJ1cmwiOiJodHRwczovL2FwaS53ZWlyYXBwLmlvL21jcCJ9) in one click. Cursor asks you to sign in the first time it connects. Cursor uses OAuth with Weir. It ignores an `Authorization` header in this file while the server offers sign-in, so a key here does nothing. **Check:** the server shows as connected in Cursor's MCP settings. Then ask "What's my Weir usage?" [Cursor MCP docs](https://cursor.com/docs/mcp) ## VS Code GitHub Copilot in VS Code. In `.vscode/mcp.json`, or your user `mcp.json`. The top-level key is `servers`, and the transport has to be stated: ```json { "servers": { "weir": { "type": "http", "url": "https://api.weirapp.io/mcp" } } } ``` Or from a terminal: ```bash code --add-mcp '{"name":"weir","type":"http","url":"https://api.weirapp.io/mcp"}' ``` Start the server and sign in when VS Code asks. **With a key**, let VS Code prompt for it so it never sits in the file: ```json { "inputs": [ { "type": "promptString", "id": "weir-api-key", "description": "Weir API key", "password": true } ], "servers": { "weir": { "type": "http", "url": "https://api.weirapp.io/mcp", "headers": { "Authorization": "Bearer ${input:weir-api-key}" } } } } ``` **Check:** ask Copilot in agent mode "What's my Weir usage?" [VS Code MCP configuration](https://code.visualstudio.com/docs/agents/reference/mcp-configuration) ## Devin Desktop Windsurf is now Devin Desktop, and its MCP config moved. In `~/.config/devin/mcp_config.json`, or `%APPDATA%\devin\mcp_config.json` on Windows: ```json { "mcpServers": { "weir": { "serverUrl": "https://api.weirapp.io/mcp" } } } ``` Devin Desktop signs in on first use. **With a key**, add a header that reads the environment: ```json { "mcpServers": { "weir": { "serverUrl": "https://api.weirapp.io/mcp", "headers": { "Authorization": "Bearer ${env:WEIR_API_KEY}" } } } } ``` Cascade allows 100 tools in total across all servers. **Check:** ask Cascade "What's my Weir usage?" [Devin Desktop MCP docs](https://docs.devin.ai/desktop/cascade/mcp) ## Gemini CLI ```bash gemini mcp add --transport http -s user weir https://api.weirapp.io/mcp ``` Gemini CLI signs in when the server first asks for it. Run `/mcp auth weir` to sign in again. **With a key:** ```bash gemini mcp add --transport http -s user --header "Authorization: Bearer $WEIR_API_KEY" weir https://api.weirapp.io/mcp ``` Gemini CLI names the tools `mcp_weir_`. **Check:** ask "What's my Weir usage?" [Gemini CLI MCP docs](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md) ## Zed In Zed's `settings.json`: ```json { "context_servers": { "weir": { "url": "https://api.weirapp.io/mcp" } } } ``` Zed signs in with OAuth. To use a key instead, add `"headers": { "Authorization": "Bearer weir_live_..." }` to the entry. Zed then skips sign-in, and the key sits in the file as written. **Check:** ask the agent panel "What's my Weir usage?" [Zed MCP docs](https://zed.dev/docs/ai/mcp) ## Cline In Cline's MCP settings file, `cline_mcp_settings.json`: ```json { "mcpServers": { "weir": { "type": "streamableHttp", "url": "https://api.weirapp.io/mcp" } } } ``` The `type` line matters. Without it Cline uses the older SSE transport and cannot connect. Cline signs in on first use. **With a key**, from the Cline CLI: ```bash cline mcp add weir https://api.weirapp.io/mcp --transport http --header "Authorization: Bearer $WEIR_API_KEY" --yes ``` **Check:** ask "What's my Weir usage?" [Cline MCP docs](https://docs.cline.bot/mcp/mcp-overview) ## Goose In `~/.config/goose/config.yaml`: ```yaml extensions: weir: name: Weir type: streamable_http uri: https://api.weirapp.io/mcp enabled: true timeout: 300 ``` Or [add it to Goose](goose://extension?url=https%3A%2F%2Fapi.weirapp.io%2Fmcp&type=streamable_http&id=weir&name=Weir&description=Public+Reddit+and+X+data&timeout=300) in one click. Goose signs in on first use. **With a key**, add a header and let Goose read the variable: ```yaml extensions: weir: name: Weir type: streamable_http uri: https://api.weirapp.io/mcp headers: Authorization: "Bearer ${WEIR_API_KEY}" env_keys: [WEIR_API_KEY] enabled: true timeout: 300 ``` **Check:** ask "What's my Weir usage?" [Goose](https://github.com/aaif-goose/goose) ## Continue Continue's IDE extensions sign in with OAuth only over SSE, so with Weir they use a key. In `config.yaml`: ```yaml mcpServers: - name: Weir type: streamable-http url: https://api.weirapp.io/mcp apiKey: ${{ secrets.WEIR_API_KEY }} ``` Continue sends `apiKey` as `Authorization: Bearer `. It reads secrets from its `.env` files, not from your shell, so put `WEIR_API_KEY` there. **Check:** ask "What's my Weir usage?" [Continue](https://github.com/continuedev/continue) ## Anything else A client that can only start local stdio servers can reach Weir through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote), a third-party bridge that runs the sign-in itself: ```json { "mcpServers": { "weir": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.weirapp.io/mcp"] } } } ``` Every client above connects directly, so none of them needs it. --- # APIs and frameworks Model APIs and agent frameworks run without a person at a browser, so they connect with a Weir API key, sent as `Authorization: Bearer `. Create one under [API keys](https://weirapp.io/dashboard/keys) and keep it in `WEIR_API_KEY`. Each example asks "What's my Weir usage?" A working connection calls `get_usage`, which is free. ## Claude API The Messages API connects to remote MCP servers itself, from Anthropic's cloud, so the URL has to be public. Pass the key as `authorization_token`: ```python import os import anthropic client = anthropic.Anthropic() response = client.beta.messages.create( model="claude-opus-5-5", max_tokens=1024, betas=["mcp-client-2025-11-20"], mcp_servers=[ { "type": "url", "url": "https://api.weirapp.io/mcp", "name": "weir", "authorization_token": os.environ["WEIR_API_KEY"], } ], tools=[{"type": "mcp_toolset", "mcp_server_name": "weir"}], messages=[{"role": "user", "content": "What's my Weir usage?"}], ) ``` Both halves are required: the server in `mcp_servers`, and an `mcp_toolset` that names it. [Claude MCP connector docs](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector) ## OpenAI Responses API OpenAI calls Weir from its own servers. Send the key in `headers`: ```python import os from openai import OpenAI client = OpenAI() response = client.responses.create( model=os.environ["OPENAI_MODEL"], # any model that supports remote MCP tools=[ { "type": "mcp", "server_label": "weir", "server_url": "https://api.weirapp.io/mcp", "headers": {"Authorization": f"Bearer {os.environ['WEIR_API_KEY']}"}, "require_approval": {"never": {"read_only": True}}, } ], input="What's my Weir usage?", ) print(response.output_text) ``` `require_approval` decides which calls stop and wait for your approval. The setting above lets tools marked read-only run straight away and asks before anything else. Leave it out and every call waits for approval. The MCP tool is part of the Responses API. Chat Completions does not have it. [OpenAI MCP docs](https://developers.openai.com/api/docs/guides/tools-connectors-mcp) ## OpenAI Agents SDK Your process connects to Weir: ```python import asyncio import os from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp async def main() -> None: async with MCPServerStreamableHttp( name="weir", params={ "url": "https://api.weirapp.io/mcp", "headers": {"Authorization": f"Bearer {os.environ['WEIR_API_KEY']}"}, "timeout": 30, }, client_session_timeout_seconds=30, cache_tools_list=True, ) as weir: agent = Agent(name="analyst", mcp_servers=[weir]) result = await Runner.run(agent, "What's my Weir usage?") print(result.final_output) asyncio.run(main()) ``` Raise the timeouts as shown. The SDK's default is five seconds, and a search or a comment tree can take longer. The JavaScript SDK's `MCPServerStreamableHttp` takes the same header under `requestInit.headers`. [Agents SDK MCP docs](https://openai.github.io/openai-agents-python/mcp/) ## LangChain With `langchain-mcp-adapters`: ```python import asyncio import os from langchain_mcp_adapters.client import MultiServerMCPClient async def main() -> None: client = MultiServerMCPClient( { "weir": { "transport": "streamable_http", "url": "https://api.weirapp.io/mcp", "headers": {"Authorization": f"Bearer {os.environ['WEIR_API_KEY']}"}, } } ) tools = await client.get_tools() print([tool.name for tool in tools]) asyncio.run(main()) ``` LangChain prefixes the tool names with the server name. In JavaScript, `@langchain/mcp-adapters` takes the same `url` and `headers`. [LangChain MCP docs](https://docs.langchain.com/oss/python/langchain/mcp/connections) ## Vercel AI SDK ```ts import { createMCPClient } from "@ai-sdk/mcp"; const weir = await createMCPClient({ transport: { type: "http", url: "https://api.weirapp.io/mcp", headers: { Authorization: `Bearer ${process.env.WEIR_API_KEY}` }, }, }); const tools = await weir.tools(); ``` Pass `tools` to `generateText` or `streamText`, and call `weir.close()` when you are done. `createMCPClient` comes from `@ai-sdk/mcp`, not from `ai`. [AI SDK MCP docs](https://ai-sdk.dev/docs/ai-sdk-core/mcp-tools) ## n8n Add an **MCP Client Tool** node to your AI agent and set: | Setting | Value | | ---------------- | ----------------------------------------------- | | Endpoint | `https://api.weirapp.io/mcp` | | Server Transport | HTTP Streamable | | Authentication | Bearer Auth, with a credential holding your key | | Tools to Include | All | Older workflows default the transport to SSE, which Weir does not serve. Switch it to HTTP Streamable. n8n's MCP OAuth2 credential also works, if you would rather sign in than store a key. [n8n MCP Client Tool docs](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp/) --- # Files for agents Machine-readable files, each for a different consumer. | File | For | | ------------------------------------------------------ | ------------------------------------------------------------ | | [`/SKILL.md`](https://weirapp.io/SKILL.md) | The agent manifest. Written for a model. | | [`/llms.txt`](https://weirapp.io/llms.txt) | A short index: what Weir is, how to connect, every endpoint. | | [`/llms-full.txt`](https://weirapp.io/llms-full.txt) | All of these docs in one file, with every endpoint's price. | | [`/openapi.json`](https://api.weirapp.io/openapi.json) | The full contract, OpenAPI 3.1. Generate clients from it. | This documentation site serves its own `/llms.txt` and `/llms-full.txt` too, built from the same pages you are reading. ## Every page here is also Markdown Append `.md` to any URL: ``` /pagination → the page /pagination.md → the same content, as Markdown ``` Including the generated endpoint pages. It is the exact string the HTML was built from, so the two cannot disagree. ## `SKILL.md` Point an agent at it and it integrates without reading anything else. It covers what human documentation leaves out because a person infers it: when _not_ to call the API, which mistakes cost money, and which errors are a dead end. It also resolves at `/skill.md` and `/Skill.md`. ## The contract Generated from the route definitions and guarded by a test that fails the build if the two drift. - **Genuinely 3.1.** Nullability is a type union, not the removed `nullable` keyword, so X's nullable counts generate as nullable in your client. - **Every operation's price is in it.** The `x-weir-billing` extension gives the credits per call, the credits per returned item, and the rate-limit weight. The reference pages read it from there, so a price shown here is the price charged. - **Stable `operationId`s.** One name per operation, everywhere. `searchReddit` is the reference page `/reference/reddit/searchReddit` and the MCP tool `search_reddit`. ## Where to read it | Surface | For | | ------------------------------------------------------ | ------------------------------------------------ | | [Reference pages](/reference/reddit/searchReddit) | Reading. One page per operation, with its price. | | [`/openapi.json`](https://api.weirapp.io/openapi.json) | Generating a client. | | [Console](https://weirapp.io/dashboard/playground) | Running a real request and seeing what it cost. | --- # Search public Reddit posts `POST /v1/reddit/search` Posts matching a query across Reddit, or within the communities named in `subreddit` or `subreddits`. Reddit's search covers post titles and bodies only — it does not search comment text; to find mentions in comments, read a community's comment stream with `GET /v1/reddit/subreddits/{name}/comments` or watch it with a `subreddit_comments` monitor. To list a community without a keyword, use `GET /v1/reddit/subreddits/{name}/posts`. NSFW posts are excluded unless `includeNsfw` is true. **Price:** 1 credit per call, plus 3 credits per Reddit post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `search_reddit`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures"}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | yes | Reddit search syntax: words, `"exact phrases"`, `OR`, `-exclusions`, and field operators such as `title:`, `author:`, `flair:`, `site:example.com`, `url:`, `self:yes`. · 1–500 chars | | `subreddit` | string | no | Restrict the search to one community, by name without `r/`. · matches `^(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})$` | | `subreddits` | array of string | no | Restrict the search to several communities at once (up to 25). | | `sort` | string | no | `new` is arrival order; `comments` ranks by comment count. · `relevance`, `hot`, `top`, `new`, `comments`, default `"relevance"` | | `time` | string | no | Only posts created within this window. · `all`, `year`, `month`, `week`, `day`, `hour`, default `"all"` | | `includeNsfw` | boolean | no | Include results marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`. · default `false` | | `limit` | integer | no | 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Reddit posts, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].title` | string | yes | | | `data[].selftext` | string | yes | Body text. Empty for a link post. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data[].url` | string | yes | Absolute URL to the post. | | `data[].score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data[].upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data[].numComments` | integer | yes | min 0 | | `data[].over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data[].createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data[].createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data[].domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data[].isSelf` | boolean | yes | A text post rather than a link. | | `data[].urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data[].linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data[].authorFlairText` | string or null | yes | The author's flair in this community. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned to the top of the community. | | `data[].locked` | boolean | yes | New comments are disabled. | | `data[].archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data[].spoiler` | boolean | yes | | | `data[].edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data[].numCrossposts` | integer | yes | min 0 | | `data[].crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data[].removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data[].subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data[].postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data[].isVideo` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get one public Reddit post `GET /v1/reddit/posts/{id}` The post's current state: score, comment count, flair, and whether it was locked, archived, or removed (`removedByCategory`). To refresh many posts at once, use `POST /v1/reddit/lookup`; for its comments, `GET /v1/reddit/posts/{id}/comments`. **Price:** 1 credit per call, plus 3 credits per Reddit post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_reddit_post`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/posts/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix: `1vdo8we` in a post URL. · matches `^[A-Za-z0-9]{1,13}$` | ## Response One Reddit post, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data.subreddit` | string | yes | Community name without the `r/` prefix. | | `data.title` | string | yes | | | `data.selftext` | string | yes | Body text. Empty for a link post. | | `data.author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data.permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data.url` | string | yes | Absolute URL to the post. | | `data.score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data.upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data.numComments` | integer | yes | min 0 | | `data.over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data.createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data.createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data.domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data.isSelf` | boolean | yes | A text post rather than a link. | | `data.urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data.linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data.authorFlairText` | string or null | yes | The author's flair in this community. | | `data.distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data.stickied` | boolean | yes | Pinned to the top of the community. | | `data.locked` | boolean | yes | New comments are disabled. | | `data.archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data.spoiler` | boolean | yes | | | `data.edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data.numCrossposts` | integer | yes | min 0 | | `data.crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data.removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data.subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data.postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data.isVideo` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get information about a public subreddit `GET /v1/reddit/subreddits/{name}` A community's description, member count, type, language, and whether it is NSFW or quarantined. A community that does not exist is a 404; one that is private, banned, quarantined, or otherwise not publicly readable is a 404 whose `details.reason` says which. **Price:** 1 credit per call, plus 1 credit per subreddit returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_subreddit`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/subreddits/NAME \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | Community name without the `r/` prefix: 3-21 letters, digits, or underscores starting with a letter or digit, or a two-letter language community such as `de`. · matches `^(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})$` | ## Response Subreddit metadata, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | Reddit's fullname for the community, `t5_` included. | | `data.name` | string | yes | Community name without the `r/` prefix. | | `data.title` | string | yes | | | `data.description` | string or null | yes | The short public description. Null if unset. | | `data.url` | string | yes | | | `data.subscribers` | integer | yes | min 0 | | `data.over18` | boolean | yes | The whole community is marked NSFW. | | `data.createdUtc` | number | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.subredditType` | string | yes | `public`, `restricted`, `private`, `archived`, `gold_restricted`, or `user`. | | `data.submissionType` | string or null | yes | What may be posted: `any`, `link`, or `self`. | | `data.lang` | string or null | yes | The community's declared language, such as `en`. | | `data.quarantine` | boolean | yes | Quarantined by Reddit: hidden from search and listings. | | `data.communityIcon` | string or null | yes | URL of the community icon. Null if unset. | | `data.bannerImg` | string or null | yes | URL of the banner image. Null if unset. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Search posts within one subreddit `POST /v1/reddit/subreddits/{name}/search` Posts matching a query inside the community in the path. The body cannot widen the search beyond it. To find communities rather than posts, use `POST /v1/reddit/search/subreddits`. **Price:** 1 credit per call, plus 3 credits per Reddit post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `search_subreddit`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/subreddits/NAME/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures"}' ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | Community name without the `r/` prefix: 3-21 letters, digits, or underscores starting with a letter or digit, or a two-letter language community such as `de`. · matches `^(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})$` | ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | yes | Reddit search syntax: words, `"exact phrases"`, `OR`, `-exclusions`, and field operators such as `title:`, `author:`, `flair:`, `site:example.com`, `url:`, `self:yes`. · 1–500 chars | | `sort` | string | no | `new` is arrival order; `comments` ranks by comment count. · `relevance`, `hot`, `top`, `new`, `comments`, default `"relevance"` | | `time` | string | no | Only posts created within this window. · `all`, `year`, `month`, `week`, `day`, `hour`, default `"all"` | | `includeNsfw` | boolean | no | Include results marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`. · default `false` | | `limit` | integer | no | 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Reddit posts restricted to the named subreddit. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].title` | string | yes | | | `data[].selftext` | string | yes | Body text. Empty for a link post. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data[].url` | string | yes | Absolute URL to the post. | | `data[].score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data[].upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data[].numComments` | integer | yes | min 0 | | `data[].over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data[].createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data[].createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data[].domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data[].isSelf` | boolean | yes | A text post rather than a link. | | `data[].urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data[].linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data[].authorFlairText` | string or null | yes | The author's flair in this community. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned to the top of the community. | | `data[].locked` | boolean | yes | New comments are disabled. | | `data[].archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data[].spoiler` | boolean | yes | | | `data[].edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data[].numCrossposts` | integer | yes | min 0 | | `data[].crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data[].removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data[].subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data[].postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data[].isVideo` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get a Reddit comment tree as a flat list, page by page `GET /v1/reddit/posts/{id}/comments` A post's comments, flattened depth-first: each carries `depth` (0 is top-level) and `parentId`, so the tree can be rebuilt. The first page is the thread as Reddit lays it out, up to `limit` comments and `depth` levels. Large threads do not fit on one page: Reddit stands in for the rest with "load more" links, and `meta.moreCount` estimates how many comments sit behind them. Pass `meta.nextCursor` back to load them, up to 100 per page, until it is null. Later pages contain only the newly loaded comments, each still carrying its `depth` and `parentId`. `comment` focuses the thread on one comment, as its permalink does, with `context` ancestors above it. **Price:** 1 credit per call, plus 3 credits per Reddit comment returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 4 requests against your per-minute rate limit. **MCP tool:** `get_reddit_comments`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/posts/ID/comments \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix: `1vdo8we` in a post URL. · matches `^[A-Za-z0-9]{1,13}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Comments on the first page; Reddit's own ceiling is 500. Each later page loads up to 100 more of the comments Reddit left behind "load more" links. · 1–500, default `50` | | `depth` | integer | no | Reply levels to include; 1 is top-level comments only. · 1–10, default `3` | | `sort` | string | no | `confidence` is Reddit's "best"; `new` puts the latest first. · `confidence`, `top`, `new`, `controversial`, `old`, `random`, `qa`, `live`, default `"confidence"` | | `comment` | string | no | Focus on one comment (its id, without `t1_`): returns it, up to `context` of its ancestors, and its replies — the view a comment's permalink opens. · matches `^[A-Za-z0-9]{1,13}$` | | `context` | integer or null | no | Ancestors to include above `comment`. Default 3. · 0–8 | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Comments ordered by Reddit with depth and parent IDs. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 comment id, without the `t1_` prefix. | | `data[].postId` | string | yes | The post this comment belongs to, without `t3_`. | | `data[].parentId` | string | yes | The comment or post this replies to. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].body` | string | yes | Markdown. `[removed]` or `[deleted]` once taken down. | | `data[].score` | integer | yes | | | `data[].permalink` | string | yes | Absolute URL of the comment. | | `data[].createdUtc` | number | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].isSubmitter` | boolean | yes | The commenter is the post's author (OP). | | `data[].edited` | number or null | yes | When the comment was last edited, in epoch seconds. Null if it never was. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned under the post by a moderator. | | `data[].scoreHidden` | boolean | yes | Reddit is hiding this comment's score for now; `score` means nothing while true. | | `data[].depth` | integer | yes | 0 for a top-level comment. · min 0 | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.moreCount` | integer | yes | Reddit's estimate of the comments not loaded yet. 0 once `nextCursor` is null. · min 0 | | `meta.focalCommentId` | string | no | The comment the thread is focused on, when `comment` was given. | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # List a community's posts: hot, new, rising, top, or controversial `GET /v1/reddit/subreddits/{name}/posts` A community's own listing, with no keyword: everything new in r/saas, or this week's top posts in r/startups. Use this rather than search when the community is the filter — Reddit's search needs a query and cannot express "everything". `name` also takes `popular`, `all`, or several communities joined with `+`, which reads them as one listing in a single call (and is billed as one request). Pages are newest-cursor-forward and stop at about 1,000 posts, Reddit's own ceiling, where `nextCursor` becomes null. NSFW posts are left out unless `includeNsfw=true`; how many were left out is in `meta.omittedNsfw`. A community that does not exist, or is private, banned, or quarantined, is a 404 whose `details.reason` says which. **Price:** 1 credit per call, plus 3 credits per Reddit post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_subreddit_posts`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/subreddits/NAME/posts \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | A community name without `r/`; `popular` or `all` for the site-wide listings; or up to 25 communities joined with `+` (`saas+startups+entrepreneur`) to read them as one listing in a single call. · matches `^(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})(?:\+(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})){0,24}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sort` | string | no | Reddit's own orderings. `new` is arrival order, which is what watching a community needs; `hot` is Reddit's default front page. · `hot`, `new`, `rising`, `top`, `controversial`, default `"hot"` | | `time` | string | no | Window for `top` and `controversial` only. Defaults to `day` for those sorts. · `hour`, `day`, `week`, `month`, `year`, `all` | | `geo` | string | no | Region for `popular` sorted by `hot` only: a country code (`DE`), a US state (`US_CA`), or `GLOBAL`. · matches `^(?:GLOBAL\|[A-Z]{2}\|US_[A-Z]{2})$` | | `includeNsfw` | string | no | Include posts marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`, so a short or empty page is never mistaken for a quiet one. · `true`, `false`, default `"false"` | | `limit` | integer | no | Posts per page, before the NSFW rule is applied. · 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Posts in the listing's order, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].title` | string | yes | | | `data[].selftext` | string | yes | Body text. Empty for a link post. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data[].url` | string | yes | Absolute URL to the post. | | `data[].score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data[].upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data[].numComments` | integer | yes | min 0 | | `data[].over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data[].createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data[].createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data[].domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data[].isSelf` | boolean | yes | A text post rather than a link. | | `data[].urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data[].linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data[].authorFlairText` | string or null | yes | The author's flair in this community. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned to the top of the community. | | `data[].locked` | boolean | yes | New comments are disabled. | | `data[].archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data[].spoiler` | boolean | yes | | | `data[].edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data[].numCrossposts` | integer | yes | min 0 | | `data[].crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data[].removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data[].subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data[].postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data[].isVideo` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Read a community's newest comments, across every post `GET /v1/reddit/subreddits/{name}/comments` Every new comment in a community, newest first, each with the post it belongs to (`linkTitle`, `linkPermalink`). This is the way to find mentions inside comments: Reddit's search does not search comment text at all. Busy communities produce comments faster than anyone pages through them; to watch one continuously, create a `subreddit_comments` monitor, which also filters by keyword. `name` takes several communities joined with `+`. NSFW is judged by the comment's post and is left out unless `includeNsfw=true`. **Price:** 1 credit per call, plus 3 credits per Reddit comment returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_subreddit_comments`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/subreddits/NAME/comments \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | A community name without `r/`; `popular` or `all` for the site-wide listings; or up to 25 communities joined with `+` (`saas+startups+entrepreneur`) to read them as one listing in a single call. · matches `^(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})(?:\+(?:[A-Za-z0-9][A-Za-z0-9_]{2,20}\|[A-Za-z]{2})){0,24}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeNsfw` | string | no | Include posts marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`, so a short or empty page is never mistaken for a quiet one. · `true`, `false`, default `"false"` | | `limit` | integer | no | Comments per page. · 1–100, default `50` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Comments, newest first, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 comment id, without the `t1_` prefix. | | `data[].postId` | string | yes | The post this comment belongs to, without `t3_`. | | `data[].parentId` | string | yes | The comment or post this replies to. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].body` | string | yes | Markdown. `[removed]` or `[deleted]` once taken down. | | `data[].score` | integer | yes | | | `data[].permalink` | string | yes | Absolute URL of the comment. | | `data[].createdUtc` | number | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].isSubmitter` | boolean | yes | The commenter is the post's author (OP). | | `data[].edited` | number or null | yes | When the comment was last edited, in epoch seconds. Null if it never was. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned under the post by a moderator. | | `data[].scoreHidden` | boolean | yes | Reddit is hiding this comment's score for now; `score` means nothing while true. | | `data[].linkTitle` | string | yes | Title of the post the comment belongs to. | | `data[].linkAuthor` | string | yes | Author of that post. | | `data[].linkPermalink` | string | yes | Absolute URL of that post. | | `data[].numComments` | integer | yes | Comments on that post. · min 0 | | `data[].over18` | boolean | yes | That post is marked NSFW. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Find communities by name or topic `POST /v1/reddit/search/subreddits` Communities whose name or description matches the query, with member counts. Use it to find where a topic is discussed before reading or watching those communities. To search *posts* inside one community, use `POST /v1/reddit/subreddits/{name}/search` instead. **Price:** 1 credit per call, plus 1 credit per subreddit returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `search_subreddits`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/search/subreddits \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures"}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | yes | Words to match against community names and descriptions. · 1–500 chars | | `sort` | string | no | `activity` favours communities that are busy right now. · `relevance`, `activity`, default `"relevance"` | | `includeNsfw` | boolean | no | Include results marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`. · default `false` | | `limit` | integer | no | 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Communities, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's fullname for the community, `t5_` included. | | `data[].name` | string | yes | Community name without the `r/` prefix. | | `data[].title` | string | yes | | | `data[].description` | string or null | yes | The short public description. Null if unset. | | `data[].url` | string | yes | | | `data[].subscribers` | integer | yes | min 0 | | `data[].over18` | boolean | yes | The whole community is marked NSFW. | | `data[].createdUtc` | number | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].subredditType` | string | yes | `public`, `restricted`, `private`, `archived`, `gold_restricted`, or `user`. | | `data[].submissionType` | string or null | yes | What may be posted: `any`, `link`, or `self`. | | `data[].lang` | string or null | yes | The community's declared language, such as `en`. | | `data[].quarantine` | boolean | yes | Quarantined by Reddit: hidden from search and listings. | | `data[].communityIcon` | string or null | yes | URL of the community icon. Null if unset. | | `data[].bannerImg` | string or null | yes | URL of the banner image. Null if unset. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get a Reddit account's public profile `GET /v1/reddit/users/{username}` Karma, account age, moderator and Premium status, and the profile's bio and follower count — the facts that tell a real, established account from a throwaway. A suspended account is returned with `isSuspended: true` and its counters null, because that is all Reddit shows; an account that does not exist is a 404. **Price:** 1 credit per call, plus 1 credit per Reddit user returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_reddit_user`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/users/USERNAME \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `username` | string | yes | Username without the `u/` prefix. · matches `^[A-Za-z0-9_-]{1,20}$` | ## Response The profile, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string or null | yes | Base-36 account id without `t2_`. Null for a suspended account. | | `data.name` | string | yes | Username without the `u/` prefix. | | `data.url` | string | yes | Absolute URL of the profile. | | `data.isSuspended` | boolean | yes | Suspended by Reddit: the profile shows nothing else. | | `data.createdUtc` | number or null | yes | When the account was created, in epoch seconds. | | `data.createdAt` | string or null | yes | | | `data.linkKarma` | integer or null | yes | Karma from posts. | | `data.commentKarma` | integer or null | yes | Karma from comments. | | `data.totalKarma` | integer or null | yes | | | `data.isEmployee` | boolean or null | yes | A Reddit employee. | | `data.isMod` | boolean or null | yes | Moderates at least one community. | | `data.isGold` | boolean or null | yes | Has Reddit Premium. | | `data.verified` | boolean or null | yes | | | `data.hasVerifiedEmail` | boolean or null | yes | | | `data.iconImg` | string or null | yes | URL of the avatar. | | `data.snoovatarImg` | string or null | yes | URL of the full-body avatar, if one is set. | | `data.acceptFollowers` | boolean or null | yes | | | `data.subreddit` | object or null | yes | The profile page itself, which Reddit models as a community named `u_`. Null for a suspended account. | | `data.subreddit.title` | string | yes | | | `data.subreddit.publicDescription` | string or null | yes | The profile's bio. | | `data.subreddit.over18` | boolean | yes | The account marked its profile NSFW. | | `data.subreddit.bannerImg` | string or null | yes | | | `data.subreddit.subscribers` | integer | yes | Followers. 0 when the account hides the count. · min 0 | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # List the posts an account has submitted `GET /v1/reddit/users/{username}/posts` Posts the account submitted, across every community, newest first by default. To be told when it posts, create a `reddit_user` monitor instead of polling. Reddit lets people hide some or all of their activity from their profile, so an empty page for an account with karma usually means hidden, not inactive. An account that does not exist is a 404, and a suspended one is a 404 with `details.reason` `suspended`. Pages stop at about 1,000 items, Reddit's own ceiling. **Price:** 1 credit per call, plus 3 credits per Reddit post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_reddit_user_posts`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/users/USERNAME/posts \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `username` | string | yes | Username without the `u/` prefix. · matches `^[A-Za-z0-9_-]{1,20}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sort` | string | no | `new` is what the account did most recently. · `new`, `hot`, `top`, `controversial`, default `"new"` | | `time` | string | no | Window for `top` and `controversial` only. Defaults to `all` for those sorts. · `hour`, `day`, `week`, `month`, `year`, `all` | | `includeNsfw` | string | no | Include posts marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`, so a short or empty page is never mistaken for a quiet one. · `true`, `false`, default `"false"` | | `limit` | integer | no | 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Posts, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].title` | string | yes | | | `data[].selftext` | string | yes | Body text. Empty for a link post. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data[].url` | string | yes | Absolute URL to the post. | | `data[].score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data[].upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data[].numComments` | integer | yes | min 0 | | `data[].over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data[].createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data[].createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data[].domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data[].isSelf` | boolean | yes | A text post rather than a link. | | `data[].urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data[].linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data[].authorFlairText` | string or null | yes | The author's flair in this community. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned to the top of the community. | | `data[].locked` | boolean | yes | New comments are disabled. | | `data[].archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data[].spoiler` | boolean | yes | | | `data[].edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data[].numCrossposts` | integer | yes | min 0 | | `data[].crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data[].removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data[].subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data[].postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data[].isVideo` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # List the comments an account has made `GET /v1/reddit/users/{username}/comments` Comments the account made, across every community, each with the post it belongs to (`linkTitle`, `linkPermalink`). Newest first by default. Reddit lets people hide some or all of their activity from their profile, so an empty page for an account with karma usually means hidden, not inactive. An account that does not exist is a 404, and a suspended one is a 404 with `details.reason` `suspended`. Pages stop at about 1,000 items, Reddit's own ceiling. **Price:** 1 credit per call, plus 3 credits per Reddit comment returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_reddit_user_comments`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/reddit/users/USERNAME/comments \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `username` | string | yes | Username without the `u/` prefix. · matches `^[A-Za-z0-9_-]{1,20}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sort` | string | no | `new` is what the account did most recently. · `new`, `hot`, `top`, `controversial`, default `"new"` | | `time` | string | no | Window for `top` and `controversial` only. Defaults to `all` for those sorts. · `hour`, `day`, `week`, `month`, `year`, `all` | | `includeNsfw` | string | no | Include posts marked NSFW. Off by default; what it removes is reported in `meta.omittedNsfw`, so a short or empty page is never mistaken for a quiet one. · `true`, `false`, default `"false"` | | `limit` | integer | no | 1–100, default `25` | | `cursor` | string | no | `meta.nextCursor` from the previous page, unchanged. Opaque, signed, and bound to the other parameters: change any of them and start again without a cursor. · 1–4096 chars | ## Response Comments, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Reddit's base-36 comment id, without the `t1_` prefix. | | `data[].postId` | string | yes | The post this comment belongs to, without `t3_`. | | `data[].parentId` | string | yes | The comment or post this replies to. | | `data[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data[].body` | string | yes | Markdown. `[removed]` or `[deleted]` once taken down. | | `data[].score` | integer | yes | | | `data[].permalink` | string | yes | Absolute URL of the comment. | | `data[].createdUtc` | number | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data[].isSubmitter` | boolean | yes | The commenter is the post's author (OP). | | `data[].edited` | number or null | yes | When the comment was last edited, in epoch seconds. Null if it never was. | | `data[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data[].stickied` | boolean | yes | Pinned under the post by a moderator. | | `data[].scoreHidden` | boolean | yes | Reddit is hiding this comment's score for now; `score` means nothing while true. | | `data[].linkTitle` | string | yes | Title of the post the comment belongs to. | | `data[].linkAuthor` | string | yes | Author of that post. | | `data[].linkPermalink` | string | yes | Absolute URL of that post. | | `data[].numComments` | integer | yes | Comments on that post. · min 0 | | `data[].over18` | boolean | yes | That post is marked NSFW. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | | `meta.omittedNsfw` | integer | no | Items removed from this page because they are marked NSFW and `includeNsfw` is false. Absent when nothing was removed. A page can be short, or even empty with a non-null `nextCursor`, because of it. · min 0 | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Fetch up to 100 posts and comments, and 100 communities, in one call `POST /v1/reddit/lookup` Current state of items you already know about: fresh scores and comment counts, whether a post was removed (`removedByCategory`) or a comment deleted (`[deleted]` body), and community metadata — in one request instead of one per item. Accepts the links people actually paste. Items Reddit did not return (deleted, nonexistent, or not publicly readable) are listed in `meta.missing` as `t3_`, `t1_`, or `r/`. Results are not filtered for NSFW: you named these items. Billed per item returned. **Price:** 1 credit per call, plus 3 credits per Reddit post and 3 credits per Reddit comment and 1 credit per subreddit returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `lookup_reddit`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/reddit/lookup \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `posts` | array of string | no | Posts, as ids (`1vdo8we`), `t3_` fullnames, post URLs, or `redd.it` links. Share links (`/s/...`) carry no id and are refused. | | `comments` | array of string | no | Comments, as ids, `t1_` fullnames, or comment permalinks. | | `subreddits` | array of string | no | Communities, by name, `r/name`, or community URL. | ## Response The items that exist and are publicly readable, in Reddit's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.posts` | array of object | yes | | | `data.posts[].id` | string | yes | Reddit's base-36 post id, without the `t3_` prefix. | | `data.posts[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data.posts[].title` | string | yes | | | `data.posts[].selftext` | string | yes | Body text. Empty for a link post. | | `data.posts[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data.posts[].permalink` | string | yes | Path on reddit.com, beginning with a slash. | | `data.posts[].url` | string | yes | Absolute URL to the post. | | `data.posts[].score` | integer | yes | Upvotes minus downvotes. Can be negative, and is fuzzed by Reddit. | | `data.posts[].upvoteRatio` | number | yes | Fraction of votes that were upvotes. · 0–1 | | `data.posts[].numComments` | integer | yes | min 0 | | `data.posts[].over18` | boolean | yes | Marked NSFW by the author or a moderator. | | `data.posts[].createdUtc` | number | yes | Creation time in epoch seconds, as Reddit reports it. | | `data.posts[].createdAt` | string (date-time) | yes | The same instant as `createdUtc`, in ISO 8601. | | `data.posts[].domain` | string | yes | `self.` for a text post; otherwise the host the post links to. | | `data.posts[].isSelf` | boolean | yes | A text post rather than a link. | | `data.posts[].urlOverriddenByDest` | string or null | yes | Where a link post points. Null for a text post. | | `data.posts[].linkFlairText` | string or null | yes | The post's flair, such as `Question` or `Hiring`. Null when it has none. | | `data.posts[].authorFlairText` | string or null | yes | The author's flair in this community. | | `data.posts[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data.posts[].stickied` | boolean | yes | Pinned to the top of the community. | | `data.posts[].locked` | boolean | yes | New comments are disabled. | | `data.posts[].archived` | boolean | yes | Past Reddit's archive age: no new comments or votes. | | `data.posts[].spoiler` | boolean | yes | | | `data.posts[].edited` | number or null | yes | When the post was last edited, in epoch seconds. Null if it never was. | | `data.posts[].numCrossposts` | integer | yes | min 0 | | `data.posts[].crosspostParent` | string or null | yes | The original post's id, without `t3_`, when this post is a crosspost. | | `data.posts[].removedByCategory` | string or null | yes | Why the post was removed: `moderator`, `deleted`, `author`, `automod_filtered`, `reddit`, and so on. Null while it is visible. | | `data.posts[].subredditSubscribers` | integer | yes | Members of the community at the time the post was read. · min 0 | | `data.posts[].postHint` | string or null | yes | What the post holds when Reddit says: `image`, `link`, `hosted:video`, `self`. | | `data.posts[].isVideo` | boolean | yes | | | `data.comments` | array of object | yes | | | `data.comments[].id` | string | yes | Reddit's base-36 comment id, without the `t1_` prefix. | | `data.comments[].postId` | string | yes | The post this comment belongs to, without `t3_`. | | `data.comments[].parentId` | string | yes | The comment or post this replies to. | | `data.comments[].author` | string | yes | Username without the `u/` prefix. `[deleted]` if removed. | | `data.comments[].body` | string | yes | Markdown. `[removed]` or `[deleted]` once taken down. | | `data.comments[].score` | integer | yes | | | `data.comments[].permalink` | string | yes | Absolute URL of the comment. | | `data.comments[].createdUtc` | number | yes | | | `data.comments[].createdAt` | string (date-time) | yes | | | `data.comments[].subreddit` | string | yes | Community name without the `r/` prefix. | | `data.comments[].isSubmitter` | boolean | yes | The commenter is the post's author (OP). | | `data.comments[].edited` | number or null | yes | When the comment was last edited, in epoch seconds. Null if it never was. | | `data.comments[].distinguished` | string or null | yes | `moderator` or `admin` when posted in an official capacity; otherwise null. | | `data.comments[].stickied` | boolean | yes | Pinned under the post by a moderator. | | `data.comments[].scoreHidden` | boolean | yes | Reddit is hiding this comment's score for now; `score` means nothing while true. | | `data.comments[].linkTitle` | string | yes | Title of the post the comment belongs to. | | `data.comments[].linkAuthor` | string | yes | Author of that post. | | `data.comments[].linkPermalink` | string | yes | Absolute URL of that post. | | `data.comments[].numComments` | integer | yes | Comments on that post. · min 0 | | `data.comments[].over18` | boolean | yes | That post is marked NSFW. | | `data.subreddits` | array of object | yes | | | `data.subreddits[].id` | string | yes | Reddit's fullname for the community, `t5_` included. | | `data.subreddits[].name` | string | yes | Community name without the `r/` prefix. | | `data.subreddits[].title` | string | yes | | | `data.subreddits[].description` | string or null | yes | The short public description. Null if unset. | | `data.subreddits[].url` | string | yes | | | `data.subreddits[].subscribers` | integer | yes | min 0 | | `data.subreddits[].over18` | boolean | yes | The whole community is marked NSFW. | | `data.subreddits[].createdUtc` | number | yes | | | `data.subreddits[].createdAt` | string (date-time) | yes | | | `data.subreddits[].subredditType` | string | yes | `public`, `restricted`, `private`, `archived`, `gold_restricted`, or `user`. | | `data.subreddits[].submissionType` | string or null | yes | What may be posted: `any`, `link`, or `self`. | | `data.subreddits[].lang` | string or null | yes | The community's declared language, such as `en`. | | `data.subreddits[].quarantine` | boolean | yes | Quarantined by Reddit: hidden from search and listings. | | `data.subreddits[].communityIcon` | string or null | yes | URL of the community icon. Null if unset. | | `data.subreddits[].bannerImg` | string or null | yes | URL of the banner image. Null if unset. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.missing` | array of string | yes | What was asked for and not returned: `t3_`, `t1_`, `r/`. | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Search public X posts with composable filters `POST /v1/x/search` Searches public posts. Every filter narrows the result, and at least one of `query`, `phrase`, `hashtags`, `fromUsers`, `toUsers`, `mentions`, `listId`, or `quotedPostId` is required. Paginate with `meta.nextCursor`. Billed per post returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `search_x`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/x/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | no | Words that must all appear, in any order. · 1–200 chars | | `phrase` | string | no | An exact phrase that must appear. · 1–200 chars | | `anyWords` | array of string | no | At least one of these words must appear. | | `excludeWords` | array of string | no | None of these words may appear. | | `hashtags` | array of string | no | Any of these hashtags, with or without the `#`. | | `fromUsers` | array of string | no | Posted by any of these usernames. | | `toUsers` | array of string | no | Replying to any of these usernames. | | `mentions` | array of string | no | Mentioning any of these usernames. | | `language` | string | no | Language X detected, as a BCP-47 tag such as `en`. · 2–12 chars | | `minLikes` | integer | no | min 0 | | `minReplies` | integer | no | min 0 | | `minReposts` | integer | no | min 0 | | `onlyLinks` | boolean | no | Only posts containing a link. | | `onlyOriginal` | boolean | no | Exclude replies. | | `onlyReplies` | boolean | no | Only replies. | | `onlyText` | boolean | no | Exclude posts with links. | | `since` | string (date-time) | no | Posted at or after this instant (UTC). | | `until` | string (date-time) | no | Posted before this instant (UTC). | | `quotedPostId` | string | no | Only posts quoting this post. `getXQuotes` is the dedicated endpoint for this. · matches `^\d{1,19}$` | | `listId` | string | no | Only posts by members of this public List. · matches `^\d{1,19}$` | | `sinceId` | string | no | Only posts newer than this post id, exclusive. The way to resume a feed: pass the newest id already seen. · matches `^\d{1,19}$` | | `untilId` | string | no | Only posts older than this post id, exclusive. · matches `^\d{1,19}$` | | `sort` | string | no | `latest` is newest first; `top` is X's own engagement ranking. · `latest`, `top`, default `"latest"` | | `limit` | integer | no | Most posts to return. X serves about 20 per page at most. · 1–100, default `25` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response X posts, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get up to 100 public X posts by id `GET /v1/x/posts` Looks up many posts in one request, for hydrating ids you already hold (from a webhook, a dataset, or another response). Posts come back in the order requested. An id that cannot be returned is listed in `meta.notFound` instead of failing the request, and is not billed. Billed per post returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_posts`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts?ids=IDS \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | string | yes | Comma-separated numeric post ids, 1 to 100, no spaces. Duplicates are ignored. · matches `^\d{1,19}(,\d{1,19}){0,99}$` | ## Response The posts found, in request order. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.notFound` | array of string | yes | Requested ids that came back empty: deleted, suspended, protected, withheld, or never existed. They cost nothing. | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get one public X post `GET /v1/x/posts/{id}` Returns one post with its author, counters, media, and any post it quotes or reposts. 404 when the post does not exist, was deleted, or is not public. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_post`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ## Response One X post, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data.text` | string | yes | Full post text, untruncated. | | `data.url` | string | yes | | | `data.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data.createdAt` | string (date-time) | yes | | | `data.conversationId` | string or null | yes | The thread this post belongs to. | | `data.inReplyToId` | string or null | yes | | | `data.author` | object | yes | | | `data.author.id` | string | yes | | | `data.author.username` | string | yes | | | `data.author.name` | string | yes | | | `data.author.verified` | boolean | yes | | | `data.author.followersCount` | integer | yes | min 0 | | `data.author.profileImageUrl` | string or null | yes | | | `data.likeCount` | integer or null | yes | min 0 | | `data.retweetCount` | integer or null | yes | min 0 | | `data.replyCount` | integer or null | yes | min 0 | | `data.viewCount` | integer or null | yes | min 0 | | `data.quoteCount` | integer or null | yes | min 0 | | `data.bookmarkCount` | integer or null | yes | min 0 | | `data.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data.media[].id` | string | yes | | | `data.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data.entities` | object | yes | | | `data.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data.quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data.quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data.quotedPost.text` | string | yes | Full post text, untruncated. | | `data.quotedPost.url` | string | yes | | | `data.quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data.quotedPost.createdAt` | string (date-time) | yes | | | `data.quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data.quotedPost.inReplyToId` | string or null | yes | | | `data.quotedPost.author` | object | yes | | | `data.quotedPost.author.id` | string | yes | | | `data.quotedPost.author.username` | string | yes | | | `data.quotedPost.author.name` | string | yes | | | `data.quotedPost.author.verified` | boolean | yes | | | `data.quotedPost.author.followersCount` | integer | yes | min 0 | | `data.quotedPost.author.profileImageUrl` | string or null | yes | | | `data.quotedPost.likeCount` | integer or null | yes | min 0 | | `data.quotedPost.retweetCount` | integer or null | yes | min 0 | | `data.quotedPost.replyCount` | integer or null | yes | min 0 | | `data.quotedPost.viewCount` | integer or null | yes | min 0 | | `data.quotedPost.quoteCount` | integer or null | yes | min 0 | | `data.quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data.quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data.quotedPost.media[].id` | string | yes | | | `data.quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data.quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data.quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data.quotedPost.entities` | object | yes | | | `data.quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data.quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data.quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data.retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data.retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data.retweetedPost.text` | string | yes | Full post text, untruncated. | | `data.retweetedPost.url` | string | yes | | | `data.retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data.retweetedPost.createdAt` | string (date-time) | yes | | | `data.retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data.retweetedPost.inReplyToId` | string or null | yes | | | `data.retweetedPost.author` | object | yes | | | `data.retweetedPost.author.id` | string | yes | | | `data.retweetedPost.author.username` | string | yes | | | `data.retweetedPost.author.name` | string | yes | | | `data.retweetedPost.author.verified` | boolean | yes | | | `data.retweetedPost.author.followersCount` | integer | yes | min 0 | | `data.retweetedPost.author.profileImageUrl` | string or null | yes | | | `data.retweetedPost.likeCount` | integer or null | yes | min 0 | | `data.retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data.retweetedPost.replyCount` | integer or null | yes | min 0 | | `data.retweetedPost.viewCount` | integer or null | yes | min 0 | | `data.retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data.retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data.retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data.retweetedPost.media[].id` | string | yes | | | `data.retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data.retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data.retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data.retweetedPost.entities` | object | yes | | | `data.retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data.retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data.retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get replies to an X post `GET /v1/x/posts/{id}/replies` Returns the replies under a post, including replies to replies. The post itself, the posts above it, and promoted posts are left out. For the author's own continuation, use `getXThread`. Billed per reply returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_replies`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID/replies \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | | `sort` | string | no | `latest` is newest first; `relevance` and `likes` are X's own rankings. · `latest`, `relevance`, `likes`, default `"latest"` | ## Response Replies, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the author's thread containing a post `GET /v1/x/posts/{id}/thread` Returns the thread a post belongs to, in reading order: the author's earlier posts it continues from, the post, and the author's continuation below it. Pass any post in the thread. Other people's replies are not included; use `getXReplies` for those. Assembled from X's first page of the conversation, so a thread long enough for X to fold behind "Show more" ends at the fold. 404 when the post does not exist. Billed per post returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_thread`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID/thread \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ## Response The thread, first post first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get posts quoting an X post `GET /v1/x/posts/{id}/quotes` Returns public posts that quote the given post, each with the quoted post embedded as `quotedPost`. Useful for tracking how a post is being discussed beyond its replies. Paginate with `meta.nextCursor`. Billed per post returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_quotes`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID/quotes \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sort` | string | no | `latest` is newest first; `top` is X's own engagement ranking. · `latest`, `top`, default `"latest"` | | `limit` | integer | no | Most posts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Quote posts, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get accounts that reposted an X post `GET /v1/x/posts/{id}/retweeters` Returns the accounts that reposted a post, as X lists them; X may cap the list for very widely reposted posts. Who liked a post is not available: X made likes private. Paginate with `meta.nextCursor`. Billed per account returned. **Price:** 1 credit per call, plus 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 3 requests against your per-minute rate limit. **MCP tool:** `get_x_retweeters`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID/retweeters \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most accounts to return. X may return fewer. · 1–100, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Accounts that reposted the post. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the edit history of an X post `GET /v1/x/posts/{id}/edits` Returns every version of an edited post, newest first, including the current one. An empty list means the post exists and was never edited. X allows edits only in the first hour after posting. 404 when the post does not exist. Billed per version returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_post_edits`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/posts/ID/edits \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric post id: the digits after `/status/` in a post's URL. · matches `^\d{1,19}$` | ## Response Versions of the post, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get up to 100 public X profiles by id `GET /v1/x/users` Looks up many accounts in one request, for hydrating ids you already hold, such as post authors or followers. Profiles come back in the order requested. An id that cannot be returned (suspended, deactivated, or never existed) is listed in `meta.notFound` and not billed. Billed per profile returned, at the same rate as `getXUser`. **Price:** 1 credit per call, plus 2 credits per X profile returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_users`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users?ids=IDS \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | string | yes | Comma-separated numeric account ids, 1 to 100, no spaces. Duplicates are ignored. · matches `^\d{1,19}(,\d{1,19}){0,99}$` | ## Response The profiles found, in request order. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.notFound` | array of string | yes | Requested ids that came back empty: deleted, suspended, protected, withheld, or never existed. They cost nothing. | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Search X accounts `POST /v1/x/users/search` Finds accounts by name, username, or bio, ranked by X's people search. Use it to turn a person or brand name into a username before calling the other account endpoints. Paginate with `meta.nextCursor`. Billed per account returned. **Price:** 1 credit per call, plus 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `search_x_users`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/x/users/search \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"payment failures"}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | yes | Free text matched against names, usernames, and bios, as X's people search does. · 1–100 chars | | `limit` | integer | no | Most accounts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Matching accounts. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get a public X profile `GET /v1/x/users/{user}` Returns one account's profile: name, bio, counters, pinned posts, and whether it is protected. A protected account's profile is public on X, so it is returned; its posts are not. 404 when the account does not exist or is suspended. **Price:** 1 credit per call, plus 2 credits per X profile returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_user`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ## Response An X profile, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | | | `data.username` | string | yes | Handle without the `@` prefix. | | `data.name` | string | yes | Display name. | | `data.description` | string or null | yes | | | `data.url` | string | yes | | | `data.profileImageUrl` | string or null | yes | | | `data.profileBannerUrl` | string or null | yes | | | `data.location` | string or null | yes | | | `data.verified` | boolean | yes | | | `data.followersCount` | integer | yes | min 0 | | `data.followingCount` | integer | yes | min 0 | | `data.statusesCount` | integer | yes | Posts published. · min 0 | | `data.likeCount` | integer | yes | min 0 | | `data.createdAt` | string (date-time) | yes | | | `data.pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data.protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an X account's "About this account" details `GET /v1/x/users/{user}/about` Returns what X shows under "About this account": the country X attributes the account to, how it connects to X, how many times its username has changed, and identity verification. Useful for judging whether an account is what it claims to be. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed as one profile, plus one more when `{user}` is a numeric id. **Price:** 1 credit per call, plus 2 credits per X profile returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_user_about`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/about \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ## Response The account's about details. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | | | `data.username` | string | yes | Handle without the `@` prefix. | | `data.name` | string | yes | Display name. | | `data.createdAt` | string (date-time) | yes | | | `data.profileImageUrl` | string or null | yes | | | `data.profileImageShape` | string or null | yes | `Circle` for people, `Square` for organisations, `Hexagon` for some others. | | `data.verified` | boolean | yes | | | `data.protected` | boolean or null | yes | | | `data.aboutProfile` | object or null | yes | | | `data.aboutProfile.accountBasedIn` | string or null | yes | The country X attributes the account to. | | `data.aboutProfile.locationAccurate` | boolean or null | yes | Whether X considers `accountBasedIn` accurate. | | `data.aboutProfile.createdCountryAccurate` | boolean or null | yes | | | `data.aboutProfile.source` | string or null | yes | How the account connects to X, in X's own words, such as an app store. | | `data.aboutProfile.usernameChanges` | object | yes | | | `data.aboutProfile.usernameChanges.count` | integer or null | yes | min 0 | | `data.aboutProfile.usernameChanges.lastChangedAt` | string or null | yes | | | `data.verificationInfo` | object or null | yes | | | `data.verificationInfo.isIdentityVerified` | boolean or null | yes | | | `data.verificationInfo.verifiedSince` | string or null | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get a public X user timeline `GET /v1/x/users/{user}/posts` Returns an account's own posts and reposts, newest first, with any pinned post first. Replies are on `getXUserReplies`. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per post returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_posts`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/posts \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most posts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Timeline posts, in X's own field names. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an X account's posts and replies `GET /v1/x/users/{user}/replies` Returns the profile's Replies tab: the account's posts together with its replies to others, newest first, with any pinned post first. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per post returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_replies`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/replies \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most posts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Posts and replies, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an X account's posts with media `GET /v1/x/users/{user}/media` Returns the profile's Media tab: the account's own posts that carry photos, videos, or GIFs, newest first. Media URLs are in each post's `media`. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per post returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_media`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/media \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most posts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Posts with media, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the posts an X account highlights `GET /v1/x/users/{user}/highlights` Returns the profile's Highlights tab: posts the account chose to feature. Only accounts with X Premium can highlight, so for others the list is empty. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per post returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_highlights`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/highlights \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most posts to return. X may return fewer. · 1–20, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Highlighted posts. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an X account's followers `GET /v1/x/users/{user}/followers` Returns accounts that follow the account, most recent first, as X lists them. X chooses the page size (about 70), so there is no `limit`; paginate with `meta.nextCursor`, which is null after the last page. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per account returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 3 requests against your per-minute rate limit. **MCP tool:** `get_x_user_followers`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/followers \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Followers, most recent first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the accounts an X account follows `GET /v1/x/users/{user}/following` Returns accounts the account follows, most recent first, as X lists them. X chooses the page size (about 70), so there is no `limit`; paginate with `meta.nextCursor`, which is null after the last page. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per account returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 3 requests against your per-minute rate limit. **MCP tool:** `get_x_user_following`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/following \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Followed accounts, most recent first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an organisation's affiliated X accounts `GET /v1/x/users/{user}/affiliates` Returns the accounts a Verified Organization lists as affiliated with it, such as its employees, products, or regional accounts. Empty for accounts that are not Verified Organizations. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per account returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_affiliates`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/affiliates \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most accounts to return. X may return fewer. · 1–100, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Affiliated accounts. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the creators an X account subscribes to `GET /v1/x/users/{user}/subscriptions` Returns the creators the account pays to subscribe to, where it has not hidden them. Often empty. 404 when the account does not exist, or is protected: a protected account's posts and connections are visible only to accounts it approved. Billed per account returned. The first page also bills one profile lookup, to resolve `{user}`; later pages carry the account in the cursor and do not. **Price:** 1 credit per call, plus 2 credits per X profile and 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_user_subscriptions`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/users/USER/subscriptions \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `user` | string | yes | A username, with or without the `@`, or a numeric account id. An all-digit value is read as an id. · matches `^@?[A-Za-z0-9_]{1,50}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most accounts to return. X may return fewer. · 1–100, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Subscribed-to creators. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get a public X List `GET /v1/x/lists/{id}` Returns a List's name, description, owner, and member and follower counts. Read its members with `getXListMembers` and its timeline with `getXListPosts`. 404 when the List does not exist or is private. Billed per List returned. **Price:** 1 credit per call, plus 2 credits per X list returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_list`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/lists/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric List id: the digits after `/i/lists/` in a List's URL. · matches `^\d{1,19}$` | ## Response One X List. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | | | `data.name` | string | yes | | | `data.description` | string or null | yes | | | `data.url` | string | yes | `https://x.com/i/lists/{id}`. | | `data.createdAt` | string (date-time) | yes | | | `data.memberCount` | integer | yes | min 0 | | `data.subscriberCount` | integer | yes | Accounts following the list. · min 0 | | `data.ownerId` | string | yes | | | `data.owner` | object or null | yes | The list's owner. Null only when X omits it. | | `data.owner.id` | string | yes | | | `data.owner.username` | string | yes | | | `data.owner.name` | string | yes | | | `data.owner.verified` | boolean | yes | | | `data.owner.followersCount` | integer | yes | min 0 | | `data.owner.profileImageUrl` | string or null | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the members of a public X List `GET /v1/x/lists/{id}/members` Returns the accounts in a List, as X orders them. Lists are curated, so this is a quick way to find the accounts that matter on a topic. Paginate with `meta.nextCursor`, which is null after the last page. Billed per account returned. **Price:** 1 credit per call, plus 1 credit per X user result returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 3 requests against your per-minute rate limit. **MCP tool:** `get_x_list_members`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/lists/ID/members \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric List id: the digits after `/i/lists/` in a List's URL. · matches `^\d{1,19}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | Most accounts to return. X may return fewer. · 1–100, default `20` | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Accounts in the List. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | | | `data[].username` | string | yes | Handle without the `@` prefix. | | `data[].name` | string | yes | Display name. | | `data[].description` | string or null | yes | | | `data[].url` | string | yes | | | `data[].profileImageUrl` | string or null | yes | | | `data[].profileBannerUrl` | string or null | yes | | | `data[].location` | string or null | yes | | | `data[].verified` | boolean | yes | | | `data[].followersCount` | integer | yes | min 0 | | `data[].followingCount` | integer | yes | min 0 | | `data[].statusesCount` | integer | yes | Posts published. · min 0 | | `data[].likeCount` | integer | yes | min 0 | | `data[].createdAt` | string (date-time) | yes | | | `data[].pinnedPostIds` | array of string | yes | Ids of the posts pinned to the top of the profile, usually zero or one. | | `data[].protected` | boolean | yes | True when only approved followers can see this account's posts. Its posts, replies, media, and relationship lists are then not available through this API. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get the timeline of a public X List `GET /v1/x/lists/{id}/posts` Returns recent posts from the List's members, newest first: the same feed the List shows on X. X chooses the page size (up to about 100 posts), so there is no `limit`; paginate with `meta.nextCursor`. Billed per post returned. **Price:** 1 credit per call, plus 1 credit per X post returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `get_x_list_posts`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/lists/ID/posts \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Numeric List id: the digits after `/i/lists/` in a List's URL. · matches `^\d{1,19}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `cursor` | string | no | Opaque cursor from the previous page's `meta.nextCursor`. Pass it back unchanged, with the same path and query parameters; a cursor from another request is rejected with 400. · 1–4096 chars | ## Response Posts from the List's members, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].text` | string | yes | Full post text, untruncated. | | `data[].url` | string | yes | | | `data[].lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].createdAt` | string (date-time) | yes | | | `data[].conversationId` | string or null | yes | The thread this post belongs to. | | `data[].inReplyToId` | string or null | yes | | | `data[].author` | object | yes | | | `data[].author.id` | string | yes | | | `data[].author.username` | string | yes | | | `data[].author.name` | string | yes | | | `data[].author.verified` | boolean | yes | | | `data[].author.followersCount` | integer | yes | min 0 | | `data[].author.profileImageUrl` | string or null | yes | | | `data[].likeCount` | integer or null | yes | min 0 | | `data[].retweetCount` | integer or null | yes | min 0 | | `data[].replyCount` | integer or null | yes | min 0 | | `data[].viewCount` | integer or null | yes | min 0 | | `data[].quoteCount` | integer or null | yes | min 0 | | `data[].bookmarkCount` | integer or null | yes | min 0 | | `data[].media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].media[].id` | string | yes | | | `data[].media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].entities` | object | yes | | | `data[].entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].quotedPost` | object or null | yes | The post this one quotes, when it is a quote post. | | `data[].quotedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].quotedPost.text` | string | yes | Full post text, untruncated. | | `data[].quotedPost.url` | string | yes | | | `data[].quotedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].quotedPost.createdAt` | string (date-time) | yes | | | `data[].quotedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].quotedPost.inReplyToId` | string or null | yes | | | `data[].quotedPost.author` | object | yes | | | `data[].quotedPost.author.id` | string | yes | | | `data[].quotedPost.author.username` | string | yes | | | `data[].quotedPost.author.name` | string | yes | | | `data[].quotedPost.author.verified` | boolean | yes | | | `data[].quotedPost.author.followersCount` | integer | yes | min 0 | | `data[].quotedPost.author.profileImageUrl` | string or null | yes | | | `data[].quotedPost.likeCount` | integer or null | yes | min 0 | | `data[].quotedPost.retweetCount` | integer or null | yes | min 0 | | `data[].quotedPost.replyCount` | integer or null | yes | min 0 | | `data[].quotedPost.viewCount` | integer or null | yes | min 0 | | `data[].quotedPost.quoteCount` | integer or null | yes | min 0 | | `data[].quotedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].quotedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].quotedPost.media[].id` | string | yes | | | `data[].quotedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].quotedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].quotedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].quotedPost.entities` | object | yes | | | `data[].quotedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].quotedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].quotedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `data[].retweetedPost` | object or null | yes | Set when this post is a repost. Read the original's text and counts here: X truncates a repost's own `text` to `RT @user: …`. | | `data[].retweetedPost.id` | string | yes | Numeric post id, as a string. It exceeds 2^53. | | `data[].retweetedPost.text` | string | yes | Full post text, untruncated. | | `data[].retweetedPost.url` | string | yes | | | `data[].retweetedPost.lang` | string or null | yes | BCP-47 language tag detected by X. | | `data[].retweetedPost.createdAt` | string (date-time) | yes | | | `data[].retweetedPost.conversationId` | string or null | yes | The thread this post belongs to. | | `data[].retweetedPost.inReplyToId` | string or null | yes | | | `data[].retweetedPost.author` | object | yes | | | `data[].retweetedPost.author.id` | string | yes | | | `data[].retweetedPost.author.username` | string | yes | | | `data[].retweetedPost.author.name` | string | yes | | | `data[].retweetedPost.author.verified` | boolean | yes | | | `data[].retweetedPost.author.followersCount` | integer | yes | min 0 | | `data[].retweetedPost.author.profileImageUrl` | string or null | yes | | | `data[].retweetedPost.likeCount` | integer or null | yes | min 0 | | `data[].retweetedPost.retweetCount` | integer or null | yes | min 0 | | `data[].retweetedPost.replyCount` | integer or null | yes | min 0 | | `data[].retweetedPost.viewCount` | integer or null | yes | min 0 | | `data[].retweetedPost.quoteCount` | integer or null | yes | min 0 | | `data[].retweetedPost.bookmarkCount` | integer or null | yes | min 0 | | `data[].retweetedPost.media` | array of object | yes | Attached photos, videos, and GIFs, in display order. | | `data[].retweetedPost.media[].id` | string | yes | | | `data[].retweetedPost.media[].type` | string | yes | X's own media type names. · `photo`, `video`, `animated_gif` | | `data[].retweetedPost.media[].url` | string or null | yes | The image for a photo; the highest-bitrate MP4 for a video or GIF. Null when X offers only a stream. | | `data[].retweetedPost.media[].previewImageUrl` | string or null | yes | Poster frame for a video. Null for photos and GIFs. | | `data[].retweetedPost.entities` | object | yes | | | `data[].retweetedPost.entities.hashtags` | array of string | yes | Hashtags, without the `#`. | | `data[].retweetedPost.entities.mentions` | array of string | yes | Mentioned usernames, without the `@`. | | `data[].retweetedPost.entities.urls` | array of string | yes | Expanded link targets, not t.co short links. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # Get an X Space `GET /v1/x/spaces/{id}` Returns a Space (a live audio room): its title, state (`NotStarted`, `Running`, or `Ended`), schedule, host, speakers, and audience counts, and whether a replay is available. Listeners are included only with `listeners=true`. 404 when the Space does not exist. Billed per Space returned. **Price:** 1 credit per call, plus 2 credits per X space returned. Failed and refused calls cost nothing. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_x_space`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/x/spaces/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | yes | Space id: the code after `/i/spaces/` in a Space's URL. · matches `^[A-Za-z0-9]{8,20}$` | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `listeners` | string | no | Include the listener list. Off by default: it can be long, and it names individuals who only listened. · `true`, `false`, default `"false"` | ## Response One X Space. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string | yes | | | `data.url` | string | yes | `https://x.com/i/spaces/{id}`. | | `data.state` | string or null | yes | X's lifecycle state, such as `NotStarted`, `Running`, or `Ended`. | | `data.title` | string or null | yes | | | `data.createdAt` | string or null | yes | | | `data.scheduledStart` | string or null | yes | | | `data.startedAt` | string or null | yes | | | `data.endedAt` | string or null | yes | | | `data.updatedAt` | string or null | yes | | | `data.creatorId` | string or null | yes | | | `data.creator` | object or null | yes | | | `data.creator.id` | string | yes | | | `data.creator.username` | string | yes | | | `data.creator.name` | string | yes | | | `data.creator.verified` | boolean | yes | | | `data.creator.followersCount` | integer | yes | min 0 | | `data.creator.profileImageUrl` | string or null | yes | | | `data.isLocked` | boolean or null | yes | | | `data.disallowJoin` | boolean or null | yes | | | `data.isSpaceAvailableForReplay` | boolean or null | yes | | | `data.isSpaceAvailableForClipping` | boolean or null | yes | | | `data.totalLiveListeners` | integer or null | yes | min 0 | | `data.totalReplayWatched` | integer or null | yes | min 0 | | `data.participantCount` | integer or null | yes | min 0 | | `data.participants` | object | yes | | | `data.participants.admins` | array of object | yes | Hosts and co-hosts. | | `data.participants.admins[].id` | string or null | yes | | | `data.participants.admins[].username` | string or null | yes | | | `data.participants.admins[].name` | string or null | yes | | | `data.participants.admins[].profileImageUrl` | string or null | yes | | | `data.participants.admins[].verified` | boolean or null | yes | | | `data.participants.admins[].isMutedByAdmin` | boolean or null | yes | | | `data.participants.admins[].isMutedByGuest` | boolean or null | yes | Muted their own microphone. | | `data.participants.speakers` | array of object | yes | | | `data.participants.speakers[].id` | string or null | yes | | | `data.participants.speakers[].username` | string or null | yes | | | `data.participants.speakers[].name` | string or null | yes | | | `data.participants.speakers[].profileImageUrl` | string or null | yes | | | `data.participants.speakers[].verified` | boolean or null | yes | | | `data.participants.speakers[].isMutedByAdmin` | boolean or null | yes | | | `data.participants.speakers[].isMutedByGuest` | boolean or null | yes | Muted their own microphone. | | `data.participants.listeners` | array of object | yes | Empty unless listeners were requested with `listeners=true`. | | `data.participants.listeners[].id` | string or null | yes | | | `data.participants.listeners[].username` | string or null | yes | | | `data.participants.listeners[].name` | string or null | yes | | | `data.participants.listeners[].profileImageUrl` | string or null | yes | | | `data.participants.listeners[].verified` | boolean or null | yes | | | `data.participants.listeners[].isMutedByAdmin` | boolean or null | yes | | | `data.participants.listeners[].isMutedByGuest` | boolean or null | yes | Muted their own microphone. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | | `meta.nextCursor` | string or null | no | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The requested resource does not exist. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | | `503` | The source is temporarily unavailable. The request is safe to retry. | A source was unreachable. Retryable with backoff. | --- # List monitors `GET /v1/monitors` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `list_monitors`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/monitors \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default `25` | ## Response Monitors belonging to this account. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string (uuid) | yes | | | `data[].name` | string | yes | | | `data[].target` | object or object or object or object or object or object or object | yes | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `data[].sources` | array of string | yes | | | `data[].schedule` | string | yes | `manual`, `hourly`, `daily`, `weekly` | | `data[].status` | string | yes | `active`, `paused`, `archived` | | `data[].webhookId` | string or null | yes | | | `data[].lastRunAt` | string or null | yes | | | `data[].nextRunAt` | string or null | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Create a monitor `POST /v1/monitors` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `create_monitor`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/monitors \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"name":"…","target":"…"}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | 1–120 chars | | `target` | object or object or object or object or object or object or object | yes | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `schedule` | string | no | `manual`, `hourly`, `daily`, `weekly`, default `"daily"` | | `webhookId` | string or null | no | | ## Response The created monitor. ### `201` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string (uuid) | yes | | | `data.name` | string | yes | | | `data.target` | object or object or object or object or object or object or object | yes | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `data.sources` | array of string | yes | | | `data.schedule` | string | yes | `manual`, `hourly`, `daily`, `weekly` | | `data.status` | string | yes | `active`, `paused`, `archived` | | `data.webhookId` | string or null | yes | | | `data.lastRunAt` | string or null | yes | | | `data.nextRunAt` | string or null | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The webhook does not exist in this account. | No such resource, or it is not public. | | `409` | The tier's monitor limit was reached, or its schedules do not include hourly. `details.limit` says which ceiling. | The request conflicts with the current state of the resource. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Get one monitor `GET /v1/monitors/{id}` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_monitor`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/monitors/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ## Response The monitor. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string (uuid) | yes | | | `data.name` | string | yes | | | `data.target` | object or object or object or object or object or object or object | yes | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `data.sources` | array of string | yes | | | `data.schedule` | string | yes | `manual`, `hourly`, `daily`, `weekly` | | `data.status` | string | yes | `active`, `paused`, `archived` | | `data.webhookId` | string or null | yes | | | `data.lastRunAt` | string or null | yes | | | `data.nextRunAt` | string or null | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Update a monitor `PATCH /v1/monitors/{id}` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `update_monitor`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request PATCH \ --url https://api.weirapp.io/v1/monitors/ID \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{}' ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | no | 1–120 chars | | `target` | object or object or object or object or object or object or object | no | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `schedule` | string | no | `manual`, `hourly`, `daily`, `weekly` | | `webhookId` | string or null | no | | | `status` | string | no | `active`, `paused`, `archived` | ## Response The updated monitor. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string (uuid) | yes | | | `data.name` | string | yes | | | `data.target` | object or object or object or object or object or object or object | yes | What the monitor watches. `search` runs a keyword search across sources; `subreddit` watches one community, with a keyword or without; `x_user` watches one account's posts; `reddit_thread` and `x_thread` watch one conversation for new replies. Identifiers are normalised, so a pasted URL, an `r/` prefix, or an `@` handle are all accepted. | | `data.sources` | array of string | yes | | | `data.schedule` | string | yes | `manual`, `hourly`, `daily`, `weekly` | | `data.status` | string | yes | `active`, `paused`, `archived` | | `data.webhookId` | string or null | yes | | | `data.lastRunAt` | string or null | yes | | | `data.nextRunAt` | string or null | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor, or no such webhook, in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Delete a monitor `DELETE /v1/monitors/{id}` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `delete_monitor`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request DELETE \ --url https://api.weirapp.io/v1/monitors/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ## Response The monitor was deleted. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.deleted` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # List deduplicated results a monitor has found `GET /v1/monitors/{id}/results` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `list_monitor_results`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/monitors/ID/results \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default `25` | ## Response Results, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string (uuid) | yes | | | `data[].monitorId` | string (uuid) | yes | | | `data[].itemType` | string | yes | A thread watch finds comments; every other kind finds posts. · `post`, `comment` | | `data[].source` | string | yes | | | `data[].externalId` | string or null | yes | | | `data[].url` | string | yes | | | `data[].title` | string or null | yes | | | `data[].body` | string or null | yes | | | `data[].authorHandle` | string or null | yes | | | `data[].communityName` | string or null | yes | | | `data[].firstSeenAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # List a monitor's execution history `GET /v1/monitors/{id}/runs` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `list_monitor_runs`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/monitors/ID/runs \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default `25` | ## Response Runs, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string (uuid) | yes | | | `data[].monitorId` | string (uuid) | yes | | | `data[].status` | string | yes | | | `data[].startedAt` | string or null | yes | | | `data[].finishedAt` | string or null | yes | | | `data[].resultsFound` | integer | yes | min 0 | | `data[].newResults` | integer | yes | min 0 | | `data[].creditsUsed` | integer | yes | min 0 | | `data[].errorSummary` | string or null | yes | | | `data[].createdAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Queue a monitor run immediately `POST /v1/monitors/{id}/run` Queues the run and returns straight away. The run itself consumes credits when it executes, priced by what it finds, not by this call. **Price:** Free. **Rate limit:** Counts as 4 requests against your per-minute rate limit. **MCP tool:** `run_monitor`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/monitors/ID/run \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ## Response The run was queued, or was already queued. ### `202` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.jobId` | string | yes | | | `data.queued` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such monitor in this account. | No such resource, or it is not public. | | `409` | The monitor is paused or archived. Resume it before running it. | The request conflicts with the current state of the resource. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # List webhooks `GET /v1/webhooks` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `list_webhooks`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/webhooks \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default `25` | ## Response Webhooks belonging to this account. Secrets are never returned. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string (uuid) | yes | | | `data[].name` | string | yes | | | `data[].url` | string | yes | | | `data[].status` | string | yes | `active`, `disabled` | | `data[].events` | array of string | yes | | | `data[].failureCount` | integer | yes | min 0 | | `data[].disabledAt` | string or null | yes | | | `data[].createdAt` | string (date-time) | yes | | | `data[].updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Create a webhook `POST /v1/webhooks` The signing secret is returned exactly once, in this response. It is stored encrypted and cannot be retrieved again. To replace it, create a new webhook and delete this one. **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `create_webhook`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/webhooks \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"name":"…","url":"https://example.com"}' ``` ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | 1–80 chars | | `url` | string (uri) | yes | max 2048 chars | | `events` | array of string | no | default `["monitor.result.created"]` | ## Response The created webhook and its one-time signing secret. ### `201` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string (uuid) | yes | | | `data.name` | string | yes | | | `data.url` | string | yes | | | `data.status` | string | yes | `active`, `disabled` | | `data.events` | array of string | yes | | | `data.failureCount` | integer | yes | min 0 | | `data.disabledAt` | string or null | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.updatedAt` | string (date-time) | yes | | | `data.secret` | string | yes | Shown once. Store it now; it cannot be recovered. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation, or the URL is not a permitted destination. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `409` | The tier's webhook limit was reached. `details.limit` is the ceiling. | The request conflicts with the current state of the resource. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Update a webhook `PATCH /v1/webhooks/{id}` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `update_webhook`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request PATCH \ --url https://api.weirapp.io/v1/webhooks/ID \ --header "Authorization: Bearer $WEIR_API_KEY" \ --header 'Content-Type: application/json' \ --data '{}' ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ### Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | no | 1–80 chars | | `url` | string (uri) | no | max 2048 chars | | `events` | array of string | no | | | `status` | string | no | `active`, `disabled` | ## Response The updated webhook. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.id` | string (uuid) | yes | | | `data.name` | string | yes | | | `data.url` | string | yes | | | `data.status` | string | yes | `active`, `disabled` | | `data.events` | array of string | yes | | | `data.failureCount` | integer | yes | min 0 | | `data.disabledAt` | string or null | yes | | | `data.createdAt` | string (date-time) | yes | | | `data.updatedAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such webhook in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Delete a webhook `DELETE /v1/webhooks/{id}` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `delete_webhook`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request DELETE \ --url https://api.weirapp.io/v1/webhooks/ID \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ## Response The webhook was deleted. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.deleted` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such webhook in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Queue a test delivery `POST /v1/webhooks/{id}/test` **Price:** Free. **Rate limit:** Counts as 2 requests against your per-minute rate limit. **MCP tool:** `test_webhook`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request POST \ --url https://api.weirapp.io/v1/webhooks/ID/test \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Path parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | ## Response A signed test event was queued. ### `202` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.jobId` | string | yes | | | `data.queued` | boolean | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | No such webhook in this account. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # List recent delivery attempts `GET /v1/webhook-deliveries` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `list_webhook_deliveries`, only on [MCP](/mcp) connections granted monitor or webhook access. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/webhook-deliveries \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ### Query parameters | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default `25` | | `webhookId` | string (uuid) | no | | ## Response Delivery attempts, newest first. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | array of object | yes | | | `data[].id` | string (uuid) | yes | | | `data[].webhookId` | string (uuid) | yes | | | `data[].eventId` | string | yes | | | `data[].eventType` | string | yes | | | `data[].status` | string | yes | | | `data[].attempts` | integer | yes | min 0 | | `data[].lastStatusCode` | integer or null | yes | | | `data[].lastError` | string or null | yes | | | `data[].deliveredAt` | string or null | yes | | | `data[].createdAt` | string (date-time) | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Credit balance, holds, and this month's free grant `GET /v1/usage` Returns the caller's own credit state. Requires authentication but no additional scope, because a key that cannot discover why it is being refused cannot handle being refused. **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_usage`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/usage \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ## Response The account's credits. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.balance` | integer | yes | Credits the account holds: what is left of this month's free grant plus everything bought. Purchased credits never expire. · min 0 | | `data.reserved` | integer | yes | Credits held by this account's calls still in flight. Each is charged what it returned and gives the rest back when it finishes. · min 0 | | `data.available` | integer | yes | balance minus reserved. A billed call is admitted while this covers its minimum charge; below that, calls return 402 until credits are added. · min 0 | | `data.freeGrant` | object or null | yes | This month's free credits, which are spent before anything bought and do not roll over. Null for a workspace that does not receive them: each person's oldest workspace does. | | `data.freeGrant.granted` | integer | yes | min 0 | | `data.freeGrant.remaining` | integer | yes | min 0 | | `data.freeGrant.resetsAt` | string (date-time) | yes | When this month's free credits expire and next month's arrive. | | `data.tier` | string | yes | Raised by lifetime credit purchases, net of refunds: free, then t1 from $5, t2 from $100, t3 from $500. Spending and time never lower it. · `free`, `t1`, `t2`, `t3` | | `data.spendingPaused` | boolean | yes | True while a refund or payment dispute on the account is resolved. Billed calls return 402 until then; free calls still work. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The account no longer exists. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Ceilings imposed by the account's tier `GET /v1/limits` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_limits`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/limits \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ## Response Tier limits. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.tier` | string | yes | Raised by lifetime credit purchases, net of refunds: free, then t1 from $5, t2 from $100, t3 from $500. Spending and time never lower it. · `free`, `t1`, `t2`, `t3` | | `data.rateLimitPerMinute` | integer | yes | | | `data.maxMonitors` | integer | yes | min 0 | | `data.maxWebhooks` | integer | yes | min 0 | | `data.hourlySchedules` | boolean | yes | Whether monitors may run hourly. Daily and weekly schedules are on every tier. | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `400` | The request failed validation. | The request was malformed. The `details` object names the field. | | `401` | The API key is missing, malformed, expired, or revoked. | The key is missing, malformed, revoked, or expired. | | `402` | The account is out of credit. Not retryable: this cannot clear until the billing period resets or the plan changes. | Out of credits. **Never retry.** Only a person can clear it, by adding credits at https://weirapp.io/dashboard/billing. Retrying never will. | | `403` | The API key does not carry the scope this route requires. | The key is valid but lacks the scope this operation needs. | | `404` | The account no longer exists. | No such resource, or it is not public. | | `429` | The per-minute rate limit is exhausted. Retryable: wait for Retry-After and try again. | Rate limited. Retryable: wait for `Retry-After`, then try again. | | `500` | An unexpected error occurred. | Something failed on our side. Retryable with backoff. | --- # Identify the account and scopes behind this key `GET /v1/whoami` **Price:** Free. **Rate limit:** Counts as 1 request against your per-minute rate limit. **MCP tool:** `get_identity`, on every [MCP](/mcp) connection. Requires an API key, sent as `Authorization: Bearer `. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/v1/whoami \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ## Response Authenticated API identity. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `data` | object | yes | | | `data.accountId` | string (uuid) | yes | | | `data.apiKeyId` | string (uuid) | yes | | | `data.scopes` | array of string | yes | | | `meta` | object | yes | | | `meta.requestId` | string | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `401` | Invalid API key. | The key is missing, malformed, revoked, or expired. | | `429` | Rate limit exceeded. | Rate limited. Retryable: wait for `Retry-After`, then try again. | --- # Liveness check `GET /health` **Price:** Free, and not metered. **Rate limit:** Not rate limited. No authentication needed. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/health \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ## Response Service is alive. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | string | yes | `ok`, `degraded` | | `service` | string | yes | | | `version` | string | yes | | | `timestamp` | string (date-time) | yes | | --- # Readiness check `GET /ready` **Price:** Free, and not metered. **Rate limit:** Not rate limited. No authentication needed. ## Request ```bash curl --request GET \ --url https://api.weirapp.io/ready \ --header "Authorization: Bearer $WEIR_API_KEY" ``` ## Response Dependencies are ready. ### `200` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | string | yes | `ok`, `degraded` | | `service` | string | yes | | | `version` | string | yes | | | `timestamp` | string (date-time) | yes | | ## Errors | Status | Meaning | What to do | | --- | --- | --- | | `503` | A required dependency is unavailable. | A source was unreachable. Retryable with backoff. |