API conventions
How the /api/v1/reddit routes choose a mode, and how S'more answers errors, caches, limits, bills, times out and pages
These conventions cover the /api/v1/reddit routes and the rules they share with the rest of /api/v1. Paths below are relative to https://app.makesmores.com/api/v1. Other product families aren't covered on this page yet, so read their own pages. Each product page lists the codes particular to it.
Modes
Each Reddit resource has one path. The method and the query together pick one mode, and each parameter belongs to the mode it picks, so parameters don't combine freely. GET /reddit/topics?type=problem reads pain point groups and GET /reddit/topics?type=solution_request reads solution requests. GET /reddit/topics with no type matches no mode.
A request that matches no mode answers 400 invalid_request before S'more reads the key, and it costs nothing. That includes an unknown parameter, a parameter sent twice, a query that can't be parsed and a value that picks no mode, such as type=problems or tracked=1:
{ "code": "invalid_request", "message": "Invalid request.", "runsRemaining": 0 }
The legacy routes on REST API remain supported and keep their own parameters.
Bodies
Send bodies as JSON with Content-Type: application/json. Routes differ in how they treat unknown, repeated and null fields, so follow each route's documented fields exactly. A lead state change and a saved draft body can be at most 64 KiB.
Field names
Casing isn't the same on every route. Most Reddit answers use snake_case, such as schema_version and next_cursor. Saved drafts on /reddit/drafts answer with the camelCase names the S'more app uses, such as briefId, postedAt and createdAt. Subreddit search (GET /reddit/subreddits?query=) answers results, and its rows can carry records30d. Read each field by the exact name its route gives.
Errors
Most errors are JSON with a stable code to branch on and a message for people, which can change. Many also carry runsRemaining, which you can ignore. Errors lists the shared codes and the fields of 402 credits_required.
A few answers aren't JSON, or don't say they are:
| Status | Code | Meaning |
|---|---|---|
| 404 | None, the body is the plain text 404 page not found | A GET to a path S'more doesn't serve |
| 405 | None, the body is plain text | No route at that path takes that method. A write to an unserved path answers this too |
| 503 | request_timeout | The call ran past its deadline. The body is JSON but the Content-Type is plain text. See Timeouts |
The request_timeout body:
{ "code": "request_timeout", "message": "Request timed out." }
To parse an error safely, try any 5xx body as JSON whatever its Content-Type says, and keep the raw text if that fails. On other statuses, check Content-Type first and parse JSON only when it says application/json.
A 404 with a plain text body means the path isn't a route. A 404 with a JSON body carries a code, such as tracker_not_found or not_found, for something that doesn't exist in this workspace.
Caching
Answers from the /api/v1/reddit routes carry Cache-Control: no-store. An answer given before a route runs may not carry it, such as a 404 for a path S'more doesn't serve, a 405 for a method no route at that path takes, or a 429 from the per-IP burst limit. Don't cache those either. GET /models can be cached for 60 seconds.
Of the Reddit routes, only mentions send an ETag: GET /reddit/mentions and the legacy GET /mentions, for both the tracker list and a report. Keep the body and its tag, then send the tag back in If-None-Match on the same request. When nothing changed, S'more answers 304 with an empty body, so keep the body you saved. A tag from GET /reddit/mentions works on GET /mentions, and the other way round.
Rate limits
S'more limits bursts of calls per client IP across every /api route, and some routes add their own limit per workspace. Rate limits lists them. A call over a limit answers 429 rate_limited with Retry-After in seconds.
Answers can carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. These can describe the per-IP limit rather than the route you called, so pace calls by the status and Retry-After, not by X-RateLimit-Remaining.
Billing
Every /api/v1/reddit route bills the api.call rate: $0.001 per call. S'more deducts it once the key is read, keeps it when the answer is 2xx and refunds it when the call fails. A request refused before or at the key costs nothing: a query that matches no mode, a 404 for a path S'more doesn't serve, a 405 for a method no route at that path takes, or a 403. A 402 credits_required answers only once credits are enforced.
These routes don't appear in GET /api/v1/models, which lists models and rates.
Gates
Some Reddit products open and close on their own. Their routes answer either way: once the key is read, a closed product answers with its code.
| Capability | Modes affected | Off answer | Body | Key read first |
|---|---|---|---|---|
| Reddit signals | Subreddit lists, search, top posts and watch changes; trends; topics; leads; saved drafts | 409 reddit_signals_off | JSON | Yes |
| Reddit mentions | Every mode of /reddit/mentions | 503 reddit_mentions_disabled | JSON | Yes |
| Reddit outreach | POST /reddit/outreach | 503 outreach_disabled | JSON | Yes |
Submitting Reddit outreach isn't available yet. Until it opens, POST /reddit/outreach answers 503 outreach_disabled.
The other outreach modes, which are GET /reddit/accounts, posting context, batch reads and cancel, can answer 503 outreach_disabled as well. A 200 from GET /reddit/accounts doesn't mean you can send.
Timeouts
Each call has a deadline: 15 seconds on most routes, 30 seconds to create or edit a saved draft, and 3 minutes for posting context. A call past its deadline answers 503 request_timeout, but the work may still finish, and the call is refunded. Read the state before you retry, wherever there's state to read.
A saved draft create has no idempotency key, and no route lists saved drafts, so after a timeout there's no way to look the draft up. Sending it again may save a second copy.
Retries
S'more doesn't promise exactly-once delivery. A save with an idempotency_key answers the first result again. A save with a revision is refused with 409 revision_conflict once the revision is stale, including when your own earlier save went through, so read before you resend. Work on Reddit can still repeat.
Each outreach submit takes an idempotency_key. The same key can't create a second batch, but a new key is new work and can send again. S'more retries a row only after a failure it classifies as definite, and never automatically repeats a row whose result is uncertain. That classification can be wrong.
429: wait forRetry-After, then retry.503 request_timeout: read the state first, as above.409 reddit_signals_off,503 reddit_mentions_disabledand503 outreach_disabled: the product is closed, so retrying won't help until it opens.503 result_too_large: the answer is too big, so ask for less, such as a smallerlimit.- Other
503codes: retry with backoff. 409 revision_conflict: read again and send the change with the newrevision.- Other
4xxcodes: change the request before you send it again.
Recover from a failed or uncertain write covers each Reddit state.
Pagination
Each family pages its own way:
| Mode family | Rule |
|---|---|
| Reddit leads | limit up to 50, where 0 or none means 50. Send next_cursor back as cursor. It's null on the last page, and a cursor only works with the filters it came from |
| Reddit outreach rows | limit 1 to 100, and limit=0 is refused. Send next_cursor back as cursor, and stop when there's none |
| Mentions report | Send series.next_cursor back as series_cursor, and examples_next_cursor as examples_cursor |
| Draft batches | after and next_after, as on Batches |
| Topics and trends | No paging |
| Saved drafts | No list. Read one draft by its id |