# 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/json` on 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" }`. `401` missing or invalid key, `403` missing scope, `404` not found, `400` validation error (the message names the field), `429` a 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

1. `GET /me` to confirm the key and see its scopes.
2. `GET /projects` to find the project id.
3. `GET /opportunities?filter=top-ranked&sort=score&limit=10` to see the best Reddit posts to reply to.
4. `POST /opportunities/{id}/generate-reply` with `{ "confirmCreditSpend": true }` to draft a comment (0.1 credits).
5. `POST /queue/reply` with the opportunity id, `deliveryMethod` and the text to post it (10 credits via a warmed-up account, free from the user's own account).
6. `GET /queue/reply` to 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/reply` with `deliveryMethod: "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": true` the 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:

```json
{
  "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.

```json
{
  "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.

```json
{
  "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

## Support

support@bazzly.ai
