Machine-readable version: https://bazzly.ai/docs/api.md
Bazzly REST API
Bazzly finds Reddit posts where a product fits, drafts replies and DMs, posts comments through warmed-up accounts, and tracks everything in queues. This API exposes the same actions as the dashboard. It is designed to be driven by AI agents: every list returns plain JSON, every credit-spending action previews its cost before it charges, and the same key also works with the Bazzly MCP server at https://api.bazzly.ai/mcp.
- OpenAPI 3.1 specification: https://bazzly.ai/openapi.json
- Base URL:
https://api.bazzly.ai/public/v1 - Auth:
Authorization: Bearer <api key>on every request (X-Bazzly-Api-Key: <api key>also works) - Format: JSON in, JSON out. Send
Content-Type: application/jsonon POST, PATCH and PUT. - Keys: created under Account Settings → API & MCP, available during the free trial. Each key has scopes:
read,write,top-ups. - Errors: standard HTTP codes with a JSON body
{ "statusCode", "message" }.401missing or invalid key,403missing scope,404not found,400validation error (the message names the field),429a limit was hit: 120 requests per minute per key, or an order cap. - Access: keys work during the free trial. A cancelled subscription loses API and MCP access when its paid period ends (
401).
Quick start
GET /meto confirm the key and see its scopes.GET /projectsto find the project id.GET /opportunities?filter=top-ranked&sort=score&limit=10to see the best Reddit posts to reply to.POST /opportunities/{id}/generate-replywith{ "confirmCreditSpend": true }to draft a comment (0.1 credits).POST /queue/replywith the opportunity id,deliveryMethodand the text to post it (10 credits via a warmed-up account, free from the user's own account).GET /queue/replyto watch it go live.
Credits and confirmation
Actions that spend credits or money return a preview instead of acting until you confirm:
- Credit-spending calls (
generate-reply,generate-dm,POST /queue/replywithdeliveryMethod: "high_karma",POST /custom-tasks) return{ "requiresConfirmation": true, "costs": { ... }, "message": "..." }when called without"confirmCreditSpend": true. Repeat the call with the flag to perform it. - Money-spending calls (
POST /projects/{id}/activate,POST /projects/{id}/credits/manual) use"confirmPaymentSpend": truethe same way.
Costs: a warmed-account comment is 10 credits, an upvote 0.2 credits, an AI draft (reply or DM) 0.1 credits. Comments posted from the user's own account through the browser extension are free.
Account
GET /me
Scope: read. Returns the account and the key in use: { id, email, name, apiKey: { id, name, scopes } }.
Projects
A project is one product: its profile, watched subreddits, Google searches, filters, DM template and credits. Most accounts have one.
GET /projects
Scope: read. Returns { data: Project[] }. Project fields include id, product_name, product_url, product_description, status (active, draft, inactive, locked), icp, keywords, competitors, pain_points, key_benefits, key_features, watched_subreddits, dm_template, discover_score_threshold, discover_classifications, scoring_custom_instructions, reply_custom_instructions.
GET /projects/{projectId}
Scope: read. One project with the fields above.
POST /projects
Scope: write. Body { "url": "https://yourproduct.com" }. Bazzly analyzes the page and creates the project with a generated profile. The first project on an account is created active; later ones are created as draft and need activate. At most two drafts can exist at once.
PATCH /projects/{projectId}
Scope: write. Partial update of the profile. Accepted fields and limits:
| Field | Type | Limits |
|---|---|---|
| product_name | string | required non-empty if sent |
| product_url | string | valid URL |
| product_description | string | 70 to 500 characters |
| icp | string | 20 to 400 characters |
| keywords | string[] | up to 10, 50 chars each |
| competitors | string[] | up to 6, 50 chars each |
| pain_points | string[] | up to 5, 120 chars each |
| key_benefits | string[] | up to 5, 120 chars each |
| key_features | string[] | up to 6, 120 chars each |
| watched_subreddits | string[] | up to 10, with or without the r/ prefix |
| dm_template | string | 3 to 500 chars; variables allowed: {post_problem_summary}, {product_benefit} |
| discover_score_threshold | integer | 0 to 100, minimum fit score for opportunities |
| discover_classifications | string[] or null | which post classifications to surface |
| scoring_custom_instructions | string or null | up to 1500 chars |
| reply_custom_instructions | string or null | up to 1500 chars, guides AI reply drafts |
Unknown fields are rejected. Autopilot settings are not exposed here.
POST /projects/{projectId}/activate
Scope: top-ups. Activates a draft or inactive project. Body { "confirmPaymentSpend": true, "profile": { ...same fields as PATCH... } }. Without the flag it returns { requiresConfirmation: true, ... } describing the prorated seat charge, if any. Additional active projects are billed as extra seats at the plan price.
GET /projects/{projectId}/credits
Scope: read. Returns { balance: { monthly, topped_up, total } }. Monthly credits reset each billing period; topped-up credits never expire.
POST /projects/{projectId}/credits/manual
Scope: top-ups. Body { "amount": 50, "confirmPaymentSpend": true }. Charges the card on file at 1 credit = 1 USD; amount is 5 to 1000. Without the flag it returns a preview.
GET /projects/{projectId}/credits/auto
Scope: top-ups. Returns { auto_topup_enabled, auto_topup_threshold, auto_topup_amount }.
PUT /projects/{projectId}/credits/auto
Scope: top-ups. Body { "auto_topup_enabled": true, "auto_topup_threshold": 20, "auto_topup_amount": 50 }. Threshold 1 to 100, amount 25 to 250. Both are required when enabling. Auto top-up only fires while Autopilot is running.
Opportunities
An opportunity is a Reddit post scored against the project. Ids are prefixed: ro-{uuid} for a post found by subreddit monitoring ("recent"), sp-{uuid} for a post that ranks on Google for one of the project searches ("top-ranked"). Both kinds share the endpoints below.
GET /opportunities
Scope: read. Query: projectId (uuid, optional), filter (all, recent, top-ranked; default all), sort (score, time; default score), limit (1 to 100, default 50), offset. Returns { data: Opportunity[], total, nextOffset }. Each item includes the prefixed id, source, projectId, the post (title, body, subreddit, author, url, score, numComments, createdUtc), the match (fitScore 0 to 1, fitReason, classification), any proposedReply, and for top-ranked posts serpQuery, serpPosition and estimatedTraffic.
GET /opportunities/{id}
Scope: read. One opportunity.
PATCH /opportunities/{id}
Scope: write. Body { "proposed_reply": "..." } (3 to 4000 chars, or an empty string to clear) and/or { "skip": true } to dismiss it.
POST /opportunities/{id}/generate-reply
Scope: write. Body { "confirmCreditSpend": true }. Drafts a comment that answers the post and mentions the product where it fits, using the project's reply instructions. Returns { success, text, creditsSpent: 0.1 }. Without the flag: a cost preview.
Leads
A lead is a Reddit user who wrote a post that matched the project. DMs are sent from the user's own Reddit account by the Bazzly browser extension; the API queues them, it does not send them.
GET /leads
Scope: read. Query: projectId, sort (score, time), limit (1 to 100, default 50), offset. Returns { data: Lead[], total, nextOffset }. Each lead has id, projectId, redditUsername, bestFitScore, proposedDm, and reasons[] with the matching posts and fit reasons.
GET /leads/{id}
Scope: read. One lead.
PATCH /leads/{id}
Scope: write. Body { "proposed_dm": "..." } (3 to 2000 chars) and/or { "skip": true }.
POST /leads/{id}/generate-dm
Scope: write. Body { "confirmCreditSpend": true }. Fills the project DM template from the lead's posts. Returns { success, text, creditsSpent: 0.1 }.
Reply queue
Every requested comment. Own-account comments post when the user runs the browser extension; warmed-account comments post automatically within a few hours.
GET /queue/reply
Scope: read. Query: projectId, status (queued, posted, removed, cancelled; omit for all), limit (1 to 200, default 100), offset. Returns { data: ReplyQueueItem[], total, nextOffset } with id, status, deliveryMethod (extension or high_karma), content, redditUrl, sentAt, errorMessage, the source opportunity and any upvote order.
POST /queue/reply
Scope: write. Body:
{
"opportunityId": "ro-...",
"deliveryMethod": "high_karma",
"content": "The comment text (3 to 4000 chars). Optional if a proposed_reply is already saved.",
"upvotes": { "quantity": 5, "mode": "rank_first" },
"confirmCreditSpend": true
}
deliveryMethod is high_karma (posted through a warmed-up account, 10 credits) or extension (queued for the user's own account, free). upvotes is optional: quantity 1 to 5, mode custom or rank_first (add enough to beat the current top comment). Without confirmCreditSpend the call returns { requiresConfirmation: true, costs: { highKarmaComment, upvotes, total } }. On success: { success: true, externalTaskId | outreachQueueId, creditsSpent }.
Rules the server enforces: warmed-account comments cannot contain links; strict-on-promotion subreddits may reject warmed-account posting unless the post is asking for tools; at most 10 warmed-account orders per project per day.
POST /queue/reply/{id}/cancel
Scope: write. Cancels a queued own-account comment. Warmed-account comments cannot be retracted once queued.
POST /queue/reply/{id}/retry
Scope: write. Requeues a removed or cancelled comment. Requeuing a warmed-account comment costs 10 credits again and may require edited text.
DM queue
GET /queue/dm
Scope: read. Query: projectId, status (queued, sent, failed), limit, offset. Returns { data: DmQueueItem[], total, nextOffset }.
POST /queue/dm
Scope: write. Body { "source": "lead", "sourceId": "<lead id>" } or { "source": "opportunity", "sourceId": "ro-..." } (top-ranked posts cannot be DMed). Uses the saved proposed_dm; generate or PATCH it first. Queueing is free; the DM is sent when the user runs the extension.
POST /queue/dm/{id}/cancel
Scope: write.
POST /queue/dm/{id}/retry
Scope: write. Requeues a failed DM.
Custom tasks
POST /custom-tasks
Scope: write. Comment on any Reddit post or comment by URL through a warmed-up account, or order upvotes only.
{
"projectId": "<uuid>",
"redditUrl": "https://www.reddit.com/r/SaaS/comments/.../",
"content": "Optional comment text (3 to 4000 chars). Omit to order upvotes only.",
"upvotes": { "quantity": 3, "mode": "custom" },
"confirmCreditSpend": true
}
Same confirmation and cost rules as POST /queue/reply. Links in content are rejected.
Help center
Answer product questions from the same articles the help center renders, instead of guessing or sending the user to support.
GET /help/search
Scope: read. Query: q (2 to 200 chars), limit (1 to 10, default 5). Returns { query, results: [{ slug, title, excerpt, category, url, score, snippet }], helpCenterUrl }. The snippet is the most relevant paragraph, often enough to answer from.
GET /help/articles
Scope: read. Lists every article (slug, title, excerpt, category, url, updatedAt).
GET /help/articles/{slug}
Scope: read. One article with its full markdown body.
Feedback
POST /feedback
Scope: read (any key). Report a bug, a missing capability, a docs gap or an unanswered question on the user's behalf. Do this yourself when something blocks you; the team reads every submission and no human needs to write to support.
{
"category": "bug | missing_feature | docs | question | other",
"message": "What happened, what you expected (3 to 4000 chars)",
"projectId": "<uuid, optional>",
"context": { "endpoint": "POST /queue/reply", "status": 400, "note": "optional free-form details" }
}
Returns { id, receivedAt, message }.
MCP
The same capabilities are available as MCP tools at https://api.bazzly.ai/mcp (Streamable HTTP, bearer auth with the same key). Setup for Claude Code, Codex, Cursor and other clients: https://bazzly.ai/mcp