Your key
You were given a key that looks like sw_live_…. Send it as a bearer token on every request.
curl https://api.swello.ai/v1/accounts/swello \
-H "Authorization: Bearer sw_live_your_key_here"
Keep it server-side. It carries your whole allowance, and anyone holding it can spend it. If it leaks, ask us to reissue — we can't recover a key, only replace it.
The endpoints
Every successful response is a single data object. Fields are always present — a value we don't have is null, never missing.
{
"data": {
"object": "account",
"platform": "tiktok",
"id": "6626035987700531000",
"handle": "swello",
"display_name": "Swello",
"bio": "posting, handled.",
"avatar_url": "https://…",
"verified": false,
"private": false,
"region": "US",
"stats": {
"followers": 1234,
"following": 5,
"posts": 40,
"likes": 900
},
"status": "active",
"retrieved_at": "2026-09-02T19:00:00.000Z"
}
}
{
"data": {
"object": "video",
"id": "6977747303692078337",
"status": "live",
"url": "https://www.tiktok.com/@bob/video/6977747303692078337",
"caption": "day 14 of posting every day",
"posted_at": "2021-06-25T15:03:19.000Z",
"author": { "id": "9", "handle": "bob", "display_name": "Bob" },
"sound": { "id": "77", "title": "original sound", "author": "bob" },
"sound_start_ms": 4200,
"stats": {
"views": 18400,
"likes": 2100,
"comments": 96,
"shares": 41,
"saves": 310
},
"retrieved_at": "2026-09-02T19:00:00.000Z"
}
}
{
"accounts": ["swello", "matts.locked"],
"videos": ["6977747303692078337"]
}
{
"data": {
"object": "sweep",
"checked": 3, "alive": 2, "gone": 1, "unknown": 0,
"accounts": [
{ "handle": "swello", "status": "alive", "followers": 1234, "posts": 40 },
{ "handle": "matts.locked", "status": "gone", "followers": null, "posts": null }
],
"videos": [
{ "id": "6977747303692078337", "status": "alive", "handle": "bob", "views": 18400 }
],
"swept_at": "2026-09-02T19:00:00.000Z"
}
}
One request per item checked — a 60-account roster spends 60. Items we couldn't reach come back "unknown" and are refunded, so you only pay for answers. The X-Quota-Cost header reports what was actually billed.
A roster that would overrun your allowance is refused whole rather than half-run, so you never get a partial answer you've already paid for.
{
"data": {
"object": "usage",
"plan": "growth",
"period": "2026-09",
"used": 18422,
"included": 100000,
"remaining": 81578,
"rate_limit_per_minute": 120,
"resets_at": "2026-10-01T00:00:00.000Z",
"today": {
"date": "2026-09-02",
"used": 412,
"limit": 8000,
"remaining": 7588
}
}
}
Your limits
Your key carries up to three independent limits. /v1/usage reports where you stand on each — and it's free, so check it as often as you like.
| Limit | You'll see | Resets |
|---|---|---|
| Per minute | 429 rate_limited | Within 60 seconds |
| Per day | 429 daily_cap_reached | Midnight UTC |
| Per month | 402 quota_exhausted | resets_at says when |
The daily cap is there to protect you: a job stuck in a retry loop burns one day instead of your whole month.
Scoped keys
Some keys are restricted to a specific list of accounts. If yours is, a request for a handle outside that list returns 403 out_of_scope. For account lookups that check happens before anything else, so it costs you nothing; for videos it happens once we can see who the author is. Need an account added to your list? Ask us.
Two things that surprise people
status is the answer, not an error.
A handle or post that no longer exists comes back 200 with "status": "unavailable", not a 404. That's deliberate: if you're monitoring a roster, every row keeps the same shape and you can diff them directly. It also means a deleted post and a banned account look identical from here — we report that it's gone, and we don't guess why.
sound_start_ms belongs to the post, not the sound.
The start offset is a property of the video. A bare sound link always begins at 0:00, no matter where the original creator started it.
Errors
Every error has the same shape, so one handler covers all of them.
{ "error": { "code": "quota_exhausted", "message": "…" } }
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_handle | Fix the parameter and retry. |
| 400 | roster_too_large | A sweep takes 1–200 items. Split the roster. |
| 401 | unauthorized | Key missing, malformed, or revoked. |
| 402 | quota_exhausted | Monthly allowance spent. resets_at says when it returns. |
| 403 | out_of_scope | This key isn't permitted to read that account. |
| 429 | rate_limited | Too fast. Honour Retry-After — 60 seconds. |
| 429 | daily_cap_reached | Today's cap is spent. Resets at midnight UTC. |
| 503 | temporarily_unavailable | Our side. Retry after ~30s. You are not billed. |
Useful response headers: X-Quota-Remaining, X-RateLimit-Limit, and X-Cache (HIT or MISS).
You're billed when we return data — cache hit or fresh fetch alike. You are not billed for 429s, 503s, or /v1/usage. You never pay for our outage.
Using it from Claude
Add the Swello MCP server and Claude can answer questions about accounts and posts directly — "how many followers does @x have?", "is this post still up?". Save mcp-server.js somewhere permanent, then add this block to your config.
{
"mcpServers": {
"swello": {
"command": "/usr/local/bin/node",
"args": ["/absolute/path/to/mcp-server.js"],
"env": { "SWELLO_API_KEY": "sw_live_your_key_here" }
}
}
}
Claude Desktop takes the same block, in ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows. Restart Claude after editing.
Use absolute paths for both command and args. A Claude launched from the dock doesn't inherit your shell's PATH, so a bare node fails with ENOENT and the server simply never appears — no error, just missing tools. Find your path with which node.
Confirm it worked by asking Claude "what Swello tools do you have?" — it should list four. Then try "sweep these accounts and tell me which are gone."
| Tool | Takes | Answers |
|---|---|---|
swello_account | a handle | Follower counts, profile details, whether the account still exists. |
swello_video | a post id | Views, likes, comments, caption, and whether the post is still up. |
swello_sweep | handles and/or ids | Checks a whole roster at once — which accounts and posts are gone. |
swello_usage | nothing | Requests used and remaining this period. |
Requires Node 18 or newer. There's no npm install — the server has zero dependencies.
Getting the most from your allowance
- Responses are cached briefly — accounts about 10 minutes, videos about 5. Polling the same handle in a tight loop spends your quota and hands back identical bytes;
X-Cache: HITtells you it happened. Poll on the order of minutes, not seconds. - Check
/v1/usageon a schedule, not per request. It's free, and a daily or hourly check is enough to catch a runaway job before it drains a month. - Back off on
429. HonourRetry-Afterrather than retrying immediately — hammering a rate limit only extends it. - Batch overnight work. Your per-minute limit is separate from your monthly one, so a large roster sweep goes faster spread across minutes than fired all at once.