{"openapi": "3.1.0", "info": {"title": "Reletter API", "version": "1.0.0", "description": "Newsletter data API. Search 7M+ email newsletters, fetch metadata, contacts, social accounts and rankings, and full-text search newsletter issues. Agents can buy access programmatically over HTTP 402 / MPP without signing up.", "contact": {"name": "Reletter", "email": "support@reletter.com", "url": "https://reletter.com/llms-full.txt"}, "x-guidance": "Reletter is a newsletter data API: search 7M+ email newsletters, pull publication metadata, contacts, social accounts and rankings, and full-text search the body of every issue we have indexed. Every endpoint lives on https://api.reletter.com. The apex domain serves the marketing site.\n\nGetting a key, agent-native (no signup, no browser): POST /api/payments/buy with a JSON body naming the tier you want and no Authorization header. You get 402 Payment Required with one `WWW-Authenticate: Payment` challenge per settlement method. `stripe` is a Stripe Shared Payment Token (card or wallet, settles synchronously), `tempo` is USDC on Tempo (asynchronous). Pay, then re-POST the same tier with `Authorization: Payment <base64url credential>` echoing every field of the challenge you picked. The 200 response body carries `api_key`.\n\nThe `tempo` challenge is spec-conformant (draft-tempo-charge-00): `amount` is in TIP-20 base units, `currency` is the token contract address, and `recipient` is the on-chain Tempo deposit address. Broadcast the USDC transfer to it, then re-POST echoing the challenge with `payload` = {\"type\": \"hash\", \"hash\": \"0x...\"}; the 200 carries `api_key`. Push settlement only, so off-the-shelf MPP clients (mppx, agentcash) can pay it natively.\n\nTiers run from 5,000 requests for $59.00 to 50,000 for $429.00. GET /api/payments/bundles is the live catalogue and the only source of truth for pricing; it needs no auth.\n\nThen send the key as `x-reletter-api-key` on every /api/* call. One call spends one request from the bundle. GET /api/accounts/quota reports what is left; once it is drained calls return 400 `bundle_exhausted` and you buy another bundle. Recurring bundles are not supported.\n\nThe key is shown once. If you drop it, recover it: on the `tempo` rail POST /api/payments/recover with the PaymentIntent id and an EIP-191 signature from the funding wallet; on the `stripe` rail re-POST /api/payments/buy for the same PaymentIntent.\n\nTypical path through the data: GET /api/search/publications?query=... to find newsletters, GET /api/publications/{publication_id} for full metadata, GET /api/contacts for email and social contacts, GET /api/issues for a publication's recent issues, GET /api/search/issues to search issue text. Both search endpoints take the same `filters` grammar (`name:operator:value`, comma-joined).\n\nErrors are `{\"error\": \"...\", \"code\": \"...\"}` with a 4xx/5xx status. Full reference: https://reletter.com/llms-full.txt"}, "servers": [{"url": "https://api.reletter.com"}], "x-agentcash-guidance": {"llmsTxtUrl": "https://reletter.com/llms.txt"}, "components": {"securitySchemes": {"reletterApiKey": {"type": "apiKey", "in": "header", "name": "x-reletter-api-key", "description": "Key issued by POST /api/payments/buy, or from account settings after signing up."}}}, "paths": {"/.well-known/payments": {"get": {"summary": "Payment discovery pointers", "description": "RFC 8615 discovery document: the protocol we speak, the settlement methods on offer, and where the catalogue and purchase endpoints live. No auth.", "security": [], "responses": {"200": {"description": "Success."}}}}, "/api/payments/bundles": {"get": {"summary": "List request bundles for sale", "description": "Live catalogue of tiers, prices in USD cents, and the purchase endpoint. The source of truth for pricing. No auth.", "security": [], "responses": {"200": {"description": "Catalogue.", "content": {"application/json": {"schema": {"type": "object", "properties": {"bundles": {"type": "array", "items": {"type": "object", "properties": {"quota": {"type": "integer"}, "amount_cents": {"type": "integer"}, "currency": {"type": "string"}, "amount_display": {"type": "string"}, "description": {"type": "string"}}}}, "purchase_endpoint": {"type": "string"}, "payment_methods": {"type": "array", "items": {"type": "string"}}}}}}}}}}, "/api/payments/buy": {"post": {"summary": "Buy a bundle of API requests", "description": "Pay-per-bundle purchase over HTTP 402 / Machine Payments Protocol (mpp.dev, IETF draft draft-ryan-httpauth-payment). POST with no `Authorization` header to receive a 402 carrying one `WWW-Authenticate: Payment` challenge per settlement method (`stripe`, `tempo`). Re-POST with `Authorization: Payment <base64url credential>` echoing the challenge to settle. On success the response body carries a ready-to-use API key bound to a freshly provisioned team; no signup involved. The price depends on the tier requested; see GET /api/payments/bundles for the live catalogue.", "security": [], "x-payment-info": {"price": {"mode": "dynamic", "currency": "USD", "min": "59.00", "max": "429.00"}, "protocols": [{"mpp": {"method": "stripe", "intent": "charge", "currency": "USD"}}, {"mpp": {"method": "tempo", "intent": "charge", "currency": "USD"}}]}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["quota"], "properties": {"quota": {"type": "integer", "enum": [5000, 10000, 25000, 50000], "description": "Number of API requests to buy. Must be one of the tiers in GET /api/payments/bundles."}, "recurring": {"type": "boolean", "default": false, "description": "Not currently supported; a credential presented with `recurring: true` is rejected with `recurring_not_supported`."}}}}}}, "responses": {"200": {"description": "Payment settled. Body carries the API key.", "content": {"application/json": {"schema": {"type": "object", "properties": {"api_key": {"type": "string", "description": "Send as the `x-reletter-api-key` header."}, "api_key_header": {"type": "string"}, "base_url": {"type": "string"}, "quota": {"type": "integer"}, "used": {"type": "integer"}, "remaining": {"type": "integer"}, "recurring": {"type": "boolean"}, "expires_at": {"type": ["string", "null"]}, "bundle_id": {"type": "integer"}, "payment_intent_id": {"type": "string"}}}}}}, "400": {"description": "Body did not match the challenge, unknown tier, or recurring requested."}, "401": {"description": "Credential is malformed or does not echo a live challenge (`invalid_credential`)."}, "402": {"description": "Payment Required"}}}}, "/api/payments/recover": {"post": {"summary": "Recover the API key for a settled bundle", "description": "Re-issue the API key for a bundle you already paid for but whose key you lost. The key is shown only once, in the buy response. Only bundles settled on the `tempo` rail can be recovered here, because recovery proves control of the wallet that funded them: EIP-191 personal_sign the exact UTF-8 message 'Reletter API key recovery for <payment_intent_id>' with that wallet, then POST the PaymentIntent id and signature. The response carries the same api_key the purchase returned. An SPT/card bundle has no wallet to sign with; re-POST /api/payments/buy for the same PaymentIntent to get its key back instead.", "security": [], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["payment_intent_id", "signature"], "properties": {"payment_intent_id": {"type": "string", "description": "`payment_intent_id` from the original buy response (the Payment-Receipt header value without the `stripe:` prefix)."}, "signature": {"type": "string", "description": "0x-hex EIP-191 personal_sign of the recovery message by the funding wallet."}}}}}}, "responses": {"200": {"description": "Signature verified. Body carries the API key.", "content": {"application/json": {"schema": {"type": "object", "properties": {"api_key": {"type": "string", "description": "Send as the `x-reletter-api-key` header."}, "api_key_header": {"type": "string"}, "base_url": {"type": "string"}, "quota": {"type": "integer"}, "used": {"type": "integer"}, "remaining": {"type": "integer"}, "bundle_id": {"type": "integer"}, "payment_intent_id": {"type": "string"}, "recovered": {"type": "boolean"}}}}}}, "400": {"description": "Missing payment_intent_id or signature (`invalid_request`)."}, "401": {"description": "Signature does not match the funding wallet."}, "404": {"description": "No settled bundle for that payment_intent_id (`unknown_payment`)."}, "409": {"description": "Bundle was not wallet-funded, so it can't be recovered by signature (`recovery_unavailable`)."}}}}, "/api/payments/cancel": {"post": {"summary": "Cancel a recurring bundle", "description": "Stops future renewals on a recurring bundle. Remaining quota on the current bundle is kept. Authenticated by the API key the buy call returned.", "security": [{"reletterApiKey": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["bundle_id"], "properties": {"bundle_id": {"type": "integer", "description": "`bundle_id` from the buy response."}}}}}}, "responses": {"200": {"description": "Cancelled."}, "400": {"description": "Bundle is not recurring, or cancel failed."}, "401": {"description": "API key missing or unknown."}, "404": {"description": "Bundle not found, or not owned by this team."}}}}, "/api/accounts/quota": {"get": {"summary": "Report quota usage", "description": "Requests used and the total available on the caller's plan or bundle. Does not itself spend quota.", "security": [{"reletterApiKey": []}], "responses": {"200": {"description": "Usage.", "content": {"application/json": {"schema": {"type": "object", "properties": {"usage": {"type": "integer"}, "quota": {"type": "integer"}}}}}}, "401": {"description": "API key missing or unknown."}}}}, "/api/search/publications": {"get": {"summary": "Search newsletters", "description": "Search the index by topic, title or author, with filters for language, subscriber count, engagement, platform, publishing model and cadence.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "query", "required": true, "description": "Search query.", "schema": {"type": "string"}}, {"in": "query", "name": "mode", "required": false, "description": "What to match against.", "schema": {"type": "string", "enum": ["topics", "titles", "authors"], "default": "topics"}}, {"in": "query", "name": "page", "required": false, "description": "Page number.", "schema": {"type": "integer", "default": 1}}, {"in": "query", "name": "per_page", "required": false, "description": "Results per page (max 100).", "schema": {"type": "integer", "default": 25}}, {"in": "query", "name": "filters", "required": false, "description": "Comma-separated filters, each `name:operator:value` (max 20). Examples: `active:is:true`, `subscribers:gte:5000`, `languages:any:en`, `platforms:any:substack-beehiiv`, `sort:eq:subscribers`. Full grammar: https://reletter.com/llms-full.txt", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/search/issues": {"get": {"summary": "Full-text search newsletter issues", "description": "Search the body and titles of every indexed issue. Filters apply to the parent publication, so issue search can be scoped to e.g. active English newsletters over 5k subscribers.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "query", "required": true, "description": "Search query.", "schema": {"type": "string"}}, {"in": "query", "name": "highlight", "required": false, "description": "Include highlighted snippets.", "schema": {"type": "boolean"}}, {"in": "query", "name": "threshold", "required": false, "description": "Only issues newer than this many seconds (max 1209600).", "schema": {"type": "integer"}}, {"in": "query", "name": "page", "required": false, "description": "Page number.", "schema": {"type": "integer", "default": 1}}, {"in": "query", "name": "per_page", "required": false, "description": "Results per page (max 100).", "schema": {"type": "integer", "default": 25}}, {"in": "query", "name": "filters", "required": false, "description": "Comma-separated filters, each `name:operator:value` (max 20). Examples: `active:is:true`, `subscribers:gte:5000`, `languages:any:en`, `platforms:any:substack-beehiiv`, `sort:eq:subscribers`. Full grammar: https://reletter.com/llms-full.txt", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/search/autocomplete": {"get": {"summary": "Autocomplete a query prefix", "description": "Type-ahead suggestions for a partial query.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "query", "required": true, "description": "Query prefix.", "schema": {"type": "string"}}, {"in": "query", "name": "mode", "required": true, "description": "What to suggest.", "schema": {"type": "string", "enum": ["topics", "titles", "authors", "issues"]}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/publications/{publication_id}": {"get": {"summary": "Fetch one publication", "description": "Full metadata: subscribers, engagement, social accounts, contributors, rankings, badges, latest issues, SEO reach and domain authority.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "path", "name": "publication_id", "required": true, "description": "Reletter publication ID.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/publications": {"get": {"summary": "Batch-fetch publications", "description": "Full metadata for up to 100 publications in one call.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "publication_ids", "required": true, "description": "Comma-separated Reletter publication IDs.", "schema": {"type": "string"}}, {"in": "query", "name": "include", "required": false, "description": "Comma-separated extras: `social`, `email-contacts`, `contributors-social`.", "schema": {"type": "string"}}, {"in": "query", "name": "ignore_missing", "required": false, "description": "Skip unknown IDs instead of returning 400.", "schema": {"type": "boolean"}}, {"in": "query", "name": "suggest", "required": false, "description": "Include similar publications.", "schema": {"type": "boolean"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/issues": {"get": {"summary": "List a publication's recent issues", "description": "Up to 100 issues, newest first, including body text.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "publication_id", "required": true, "description": "Reletter publication ID.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/issues/{issue_id}": {"get": {"summary": "Fetch one issue", "description": "Full issue including body text and its parent publication.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "path", "name": "issue_id", "required": true, "description": "Reletter issue ID.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/contacts": {"get": {"summary": "Fetch a publication's contacts", "description": "Email contacts, social accounts and contributor profiles.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "query", "name": "publication_id", "required": true, "description": "Reletter publication ID.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/charts": {"get": {"summary": "List chart platforms and categories", "description": "Index of the slugs accepted by /api/charts/{platform}/{category}.", "security": [{"reletterApiKey": []}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/charts/{platform}/{category}": {"get": {"summary": "Fetch chart rankings", "description": "Publications ranked for a platform and category, in rank order.", "security": [{"reletterApiKey": []}], "parameters": [{"in": "path", "name": "platform", "required": true, "description": "Platform slug from GET /api/charts.", "schema": {"type": "string"}}, {"in": "path", "name": "category", "required": true, "description": "Category slug from GET /api/charts.", "schema": {"type": "string"}}, {"in": "query", "name": "variant", "required": false, "description": "Chart variant where supported, e.g. `paid`, `free`, `rising`.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/misc/languages": {"get": {"summary": "List language codes", "description": "Every language code the index uses, for the `languages` search filter.", "security": [{"reletterApiKey": []}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}, "/api/misc/stats": {"get": {"summary": "Report index size", "description": "Headline counts for publications and issues in the index.", "security": [{"reletterApiKey": []}], "responses": {"200": {"description": "Success."}, "400": {"description": "Validation error, or quota exhausted (`code: bundle_exhausted`)."}, "401": {"description": "API key is missing, unknown or inactive."}, "429": {"description": "Burst limit exceeded (`api_rate_limited`)."}}}}}}