Groowth API and MCP server
Read your Groowth workspace from your own code, or let an AI assistant read it for you. Accounts, daily history, posts, goals and wins, on every plan. With a write key, your code can also add and stop tracking accounts.
What the Groowth API is
The Groowth API is a REST API and MCP server for Groowth customers. It reads one workspace: its tracked accounts, their daily history, posts, posting goals and wins, and with a write key it starts and stops tracking accounts.
It is for teams and agencies that already track profiles in Groowth. Use it to feed a dashboard, a data warehouse, a client report or a script.
What it reads
- The workspace: its plan, quotas, time zone and whether it is paused.
- Folders, and how many tracked accounts each one holds.
- Each tracked account: latest followers, following and post count, and the change over 1, 7 and 30 days.
- Each account's daily history, up to 366 days per call.
- Posts, with their public counters and how each did against the account's usual post.
- Posting goals, with the progress of the current period and the streak.
- Wins: goals met, posts taking off and days of unusual follower growth.
It covers the four networks Groowth reads: LinkedIn, Instagram, TikTok and YouTube.
What a write key changes
- Start tracking an account, from its link or its handle, and file it in a folder if you like. It counts against the plan's account limit.
- Stop tracking an account. It leaves the limit, its goals are deleted and its history is kept.
What it does not do
- It never publishes, and it cannot change a goal or a folder. The only thing it changes is which accounts the workspace tracks, and only with a write key.
- It does not read a profile on demand. An account must be tracked in the workspace first, from the app or with a write key.
- It never returns reach or impressions. Groowth reads public data only, and those are private.
- It reads one workspace per key, never another one.
Authentication
Every request carries a workspace key. A key starts with groowth_sk_ and reads exactly one workspace.
Create a key
- Open Settings > API in the workspace you want to read. Only the Owner sees this page.
- Name the key after the tool that will use it. Tick the box that lets it add and stop tracking accounts only if that tool manages the accounts. Then create it.
- Copy the key right away. It is shown once, and Groowth keeps only a digest of it.
Send it in the Authorization header. Use x-api-key instead when a proxy already takes Authorization.
Authorization: Bearer groowth_sk_...
x-api-key: groowth_sk_...A key reads. A key created with write access may also start and stop tracking accounts, through the REST API only. The Owner decides when creating the key, and it cannot be changed afterwards: create another key instead. GET /workspace tells which kind a key is, in key.scope, and a write call made with a key that can only read answers 403 insufficient_scope.
A key reads everything the workspace tracks. Keep it on a server or in a secret store, never in a browser.
A workspace holds up to 10 active keys. Revoke one in Settings > API, and it stops working on its next request.
A missing, malformed, unknown or revoked key gets the same 401 unauthorized answer.
Quick start
Export your key once, so the examples below can read it:
export GROOWTH_API_KEY="groowth_sk_..."Check the key. This call returns the workspace it belongs to:
curl https://groowth.io/api/v1/workspace \
-H "Authorization: Bearer $GROOWTH_API_KEY"List the five latest Instagram posts of the workspace:
curl -G https://groowth.io/api/v1/posts \
-H "Authorization: Bearer $GROOWTH_API_KEY" \
--data-urlencode "network=instagram" \
--data-urlencode "limit=5"Every list answers with a page of data and a next_cursor. Pass that value back as cursor to read the next page.
With a write key, start tracking a profile. The answer is the account, 201 when it was added and 200 when the workspace already tracked it:
curl -X POST https://groowth.io/api/v1/accounts \
-H "Authorization: Bearer $GROOWTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"network": "instagram", "profile": "@northwind.coffee"}'Conventions
- Every endpoint lives under
/api/v1and returns JSON. Reads are GET. The two writes are a POST with a JSON body and a DELETE. - Field names are snake_case.
- Instants are ISO 8601 in UTC, like
2026-09-24T17:30:00.000Z. Instants you send need an offset orZ. - Days are calendar days written
YYYY-MM-DD, never instants. - Ids are opaque strings. Send them back exactly as you received them.
- A counter the network does not report is
null, never0.
Pagination
A list answers data and next_cursor. While next_cursor is a string, pass it as cursor and keep the other parameters the same.
Posts and wins are paged: 50 items by default, up to 100 with limit. The other lists come whole, and their next_cursor is always null.
Rate limit
Each key gets 120 requests per 60 seconds, shared by the REST API and the MCP server. Every answer carries the count:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Seconds until the window starts over. |
Retry-After | On a 429 only: seconds to wait before the next request. |
Errors
An error answers with an HTTP status and a JSON body naming its code:
{
"error": {
"code": "invalid_request",
"details": [
{
"message": "Pass `account_id` or `folder_id`, not both.",
"path": "folder_id"
}
],
"message": "Invalid request."
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter or a body field is missing, malformed or out of range, or the body is not JSON. details names it. |
| 401 | unauthorized | The key is missing, malformed, unknown or revoked. All four look the same on purpose. |
| 403 | insufficient_scope | The key can only read. Adding or stopping an account takes a key created with write access. |
| 403 | workspace_paused | The workspace is paused: its data is not served and no account can be added. GET /workspace and stopping an account still answer. |
| 404 | not_found | No such account, folder or post in this workspace, or it is no longer tracked. |
| 409 | account_limit_reached | The workspace already tracks as many accounts as its plan allows. Stop tracking one, or move to a larger plan. |
| 422 | profile_refused | The profile cannot be tracked, and trying again will not change that. reason says why: invalid_link, not_found, unsupported_network, unsupported_target. |
| 429 | rate_limited | More than 120 requests in 60 seconds with this key. Wait for Retry-After seconds. |
| 500 | internal | Something failed on our side. It is reported to us; retry later. |
| 503 | provider_unavailable | The network could not be read just now. Nothing was added; retry in a few minutes. |
A paused workspace answers 403 workspace_paused everywhere except GET /workspace, which says why, and DELETE /accounts/{account_id}.
A profile that cannot be tracked answers 422 profile_refused with one more field, reason, saying why: a link that names no profile, a profile the network says is gone, a network or a kind of page Groowth does not read.
{
"error": {
"code": "profile_refused",
"message": "The network says this profile is private, renamed or deleted.",
"reason": "not_found"
}
}Versioning
This is v1. It only ever gains fields, parameters and values.
Renaming or removing one would ship as a v2, under a new path. Ignore the fields you do not know, and your code keeps working.
Endpoints
Every read is a GET, and each one is also an MCP tool that takes the same parameters. The two writes need a write key and are REST only.
| Endpoint | MCP tool | What it does |
|---|---|---|
GET /api/v1/workspace | get_workspace | Read the workspace this key belongs to |
GET /api/v1/folders | list_folders | List the workspace's folders |
GET /api/v1/accounts | list_accounts | List the tracked accounts |
GET /api/v1/accounts/{account_id} | get_account | Read one tracked account |
GET /api/v1/accounts/{account_id}/snapshots | list_account_snapshots | Read an account's daily history |
GET /api/v1/posts | list_posts | List posts |
GET /api/v1/posts/{post_id} | get_post | Read one post |
GET /api/v1/goals | list_goals | List publishing goals |
GET /api/v1/wins | list_wins | List wins |
POST /api/v1/accounts | REST only | Start tracking an account |
DELETE /api/v1/accounts/{account_id} | REST only | Stop tracking an account |
Read the workspace this key belongs to
GET/api/v1/workspace
The workspace the key belongs to: its name, time zone, plan, quotas and whether it is paused, and whether the key may write. A key reads exactly one workspace, and this is the one. Call it first to check a key.
MCP tool: get_workspace. Answers even when the workspace is paused.
No parameters.
Example response
{
"id": "k3Qf8wPzR2mT6vYb1LxNa",
"key": {
"name": "Figue Analytics",
"prefix": "groowth_sk_Q7f2Lx",
"scope": "read"
},
"name": "Northwind Studio",
"paused": false,
"plan": "pro",
"seats": {
"limit": 3,
"used": 2
},
"slug": "northwind",
"timezone": "Europe/Paris",
"tracked_accounts": {
"limit": 10,
"used": 6
}
}List the workspace's folders
GET/api/v1/folders
Every folder of the workspace, oldest first, with how many tracked accounts each holds. A folder groups accounts (a client, a brand, a team); its id filters list_accounts and list_posts. Not paged: next_cursor is always null.
MCP tool: list_folders.
No parameters.
Example response
{
"data": [
{
"account_count": 4,
"color": "#c8a24a",
"id": "Hq2mZ8Lw1TzVn4Ra9sKeX",
"name": "Northwind"
},
{
"account_count": 2,
"color": null,
"id": "P0xY7cNd3Wb6Qe1Mv5TjA",
"name": "Side projects"
}
],
"next_cursor": null
}List the tracked accounts
GET/api/v1/accounts
Every account the workspace tracks, with its latest followers, following, post count and engagement, and its net change over 1, 7 and 30 days. Filter by folder or network. Accounts the workspace stopped tracking are not listed. Not paged: next_cursor is always null.
MCP tool: list_accounts.
| Name | Type | Description |
|---|---|---|
folder_idoptional | string | Only the accounts of this folder. |
networkoptional | one of instagram, youtube, tiktok, linkedin | Only this network. |
Example response
{
"data": [
{
"added_at": "2026-03-02T09:14:07.000Z",
"avatar_url": "https://cdn.groowth.io/avatars/instagram/northwind.webp",
"backfill_pending": false,
"bio": "Slow-roasted coffee from Lyon.",
"changes": {
"d1": {
"followers": 38,
"following": 0
},
"d30": {
"followers": 1204,
"following": 5
},
"d7": {
"followers": 311,
"following": 1
}
},
"display_name": "Northwind Coffee",
"folder_id": "Hq2mZ8Lw1TzVn4Ra9sKeX",
"handle": "northwind.coffee",
"id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"is_verified": false,
"latest": {
"date": "2026-09-28",
"engagement_rate": 3.4,
"followers": 48210,
"following": 412,
"posts_count": 961
},
"link": "https://northwind.example",
"network": "instagram",
"profile_url": "https://www.instagram.com/northwind.coffee/",
"status": "connected"
}
],
"next_cursor": null
}Read one tracked account
GET/api/v1/accounts/{account_id}
One tracked account: identity, latest measure and net change over 1, 7 and 30 days. account_id is the id returned by list_accounts.
MCP tool: get_account.
| Name | Type | Description |
|---|---|---|
account_idpath, required | string | The account's id in this workspace. |
Example response
{
"added_at": "2026-03-02T09:14:07.000Z",
"avatar_url": "https://cdn.groowth.io/avatars/instagram/northwind.webp",
"backfill_pending": false,
"bio": "Slow-roasted coffee from Lyon.",
"changes": {
"d1": {
"followers": 38,
"following": 0
},
"d30": {
"followers": 1204,
"following": 5
},
"d7": {
"followers": 311,
"following": 1
}
},
"display_name": "Northwind Coffee",
"folder_id": "Hq2mZ8Lw1TzVn4Ra9sKeX",
"handle": "northwind.coffee",
"id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"is_verified": false,
"latest": {
"date": "2026-09-28",
"engagement_rate": 3.4,
"followers": 48210,
"following": 412,
"posts_count": 961
},
"link": "https://northwind.example",
"network": "instagram",
"profile_url": "https://www.instagram.com/northwind.coffee/",
"status": "connected"
}Read an account's daily history
GET/api/v1/accounts/{account_id}/snapshots
The account's daily measures, oldest first: followers, following, post count, engagement and the lifetime totals a network reports. One row per day measured; a day the network could not be read is missing rather than guessed. Defaults to the last 90 days, at most 366 days per call. Not paged: next_cursor is always null.
MCP tool: list_account_snapshots.
| Name | Type | Description |
|---|---|---|
account_idpath, required | string | The account's id in this workspace. |
fromoptional | day, YYYY-MM-DD | First day, included. Defaults to 90 days before to. |
tooptional | day, YYYY-MM-DD | Last day, included. Defaults to today (UTC). |
Example response
{
"account_id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"data": [
{
"connections_count": null,
"date": "2026-09-27",
"engagement_rate": 3.3,
"followers": 48172,
"following": 412,
"posts_count": 960,
"total_likes": null,
"total_views": null
},
{
"connections_count": null,
"date": "2026-09-28",
"engagement_rate": 3.4,
"followers": 48210,
"following": 412,
"posts_count": 961,
"total_likes": null,
"total_views": null
}
],
"from": "2026-09-27",
"next_cursor": null,
"to": "2026-09-28"
}List posts
GET/api/v1/posts
The posts of the tracked accounts, newest first by default, with their counters and how each did against the account's usual post. Narrow by account OR folder, by network, by publication window, or to the posts changed since a date. Paged: pass next_cursor back as cursor until it is null, keeping the other parameters the same. limit defaults to 50 (20 over MCP), at most 100. To mirror posts elsewhere, page with sort=recent and updated_since set an hour before your previous run, and upsert by id. thumbnail_url is Groowth's own stable copy of the cover, on its CDN, usually within minutes of detection. Until then it may briefly be the network's URL, which expires. The copy moves the post's updated_at, so such a sync picks it up: keep the latest thumbnail_url, there is no image to copy. YouTube thumbnails are the network's own stable URLs and are not copied.
MCP tool: list_posts.
| Name | Type | Description |
|---|---|---|
account_idoptional | string | Only this account's posts. Not with folder_id. |
cursoroptional | string | The next_cursor of the previous page. |
folder_idoptional | string | Only the posts of this folder's accounts. Not with account_id. |
limitoptional | integer, 1 to 100 | Posts per page, 1 to 100. |
networkoptional | one of instagram, youtube, tiktok, linkedin | Only this network. |
published_afteroptional | instant, ISO 8601 | Published at or after this instant (ISO 8601). |
published_beforeoptional | instant, ISO 8601 | Published strictly before this instant (ISO 8601). |
sortoptional | one of recent, likes, comments, performance, default recent | recent (default, newest first: the stable order to page through everything), performance (best against the account's usual first), likes or comments. |
updated_sinceoptional | instant, ISO 8601 | Only posts changed at or after this instant (ISO 8601): counters refreshed, or the thumbnail copied to Groowth's CDN. |
Example response
{
"data": [
{
"account_id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"caption": "Our autumn roast is here. Link in bio.",
"detected_at": "2026-09-25T05:02:11.000Z",
"duration_seconds": 21,
"format": "reel",
"id": "p4Hn8QzX2cVb7Lm1Rt6Wd",
"metrics": {
"comments": 212,
"likes": 5310,
"saves": null,
"shares": null,
"views": 184000
},
"network": "instagram",
"network_post_id": "3967213292204992434",
"performance": {
"metric": "views",
"multiple": 4.7,
"settled": true,
"standout": true,
"usual": 39100,
"value": 184000
},
"published_at": "2026-09-24T17:30:00.000Z",
"thumbnail_url": "https://cdn.groowth.io/posts/instagram/3967213292204992434.webp",
"updated_at": "2026-09-29T05:03:40.000Z",
"url": "https://www.instagram.com/reel/DcOX3hWFiey/"
}
],
"next_cursor": "r_1727199000000_1727199000000_p4Hn8QzX2cVb7Lm1Rt6Wd"
}Read one post
GET/api/v1/posts/{post_id}
One post by its Groowth id, with its counters and how it did against the account's usual post.
MCP tool: get_post.
| Name | Type | Description |
|---|---|---|
post_idpath, required | string | The post's Groowth id (id in list_posts). |
Example response
{
"account_id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"caption": "Our autumn roast is here. Link in bio.",
"detected_at": "2026-09-25T05:02:11.000Z",
"duration_seconds": 21,
"format": "reel",
"id": "p4Hn8QzX2cVb7Lm1Rt6Wd",
"metrics": {
"comments": 212,
"likes": 5310,
"saves": null,
"shares": null,
"views": 184000
},
"network": "instagram",
"network_post_id": "3967213292204992434",
"performance": {
"metric": "views",
"multiple": 4.7,
"settled": true,
"standout": true,
"usual": 39100,
"value": 184000
},
"published_at": "2026-09-24T17:30:00.000Z",
"thumbnail_url": "https://cdn.groowth.io/posts/instagram/3967213292204992434.webp",
"updated_at": "2026-09-29T05:03:40.000Z",
"url": "https://www.instagram.com/reel/DcOX3hWFiey/"
}List publishing goals
GET/api/v1/goals
The workspace's publishing goals (for example 3 reels a week on one account), each with its progress over the period running now and its streak of periods met. Not paged: next_cursor is always null.
MCP tool: list_goals.
| Name | Type | Description |
|---|---|---|
account_idoptional | string | Only this account's goals. |
Example response
{
"data": [
{
"account_id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"cadence": "week",
"content_type": "reel",
"created_at": "2026-06-01T08:00:00.000Z",
"current_period": {
"done": 2,
"end": "2026-10-04T22:00:00.000Z",
"progress": 0.67,
"start": "2026-09-27T22:00:00.000Z",
"start_date": "2026-09-28",
"state": "on_track"
},
"id": "g2Wc9LmT4xQv7Nb1Rz5Kp",
"streak": 6,
"target": 3
}
],
"next_cursor": null
}List wins
GET/api/v1/wins
The workspace's wins, newest first: goals met, posts taking off and days of unusual follower growth. They are the moments the daily Wins email reports, recorded once a day around 9:00 in the workspace's time zone. Paged like list_posts: limit defaults to 50 (20 over MCP), at most 100.
MCP tool: list_wins.
| Name | Type | Description |
|---|---|---|
cursoroptional | string | The next_cursor of the previous page. |
kindoptional | one of goal_hit, post_spike, follower_spike | Only this kind of win. |
limitoptional | integer, 1 to 100 | Wins per page, 1 to 100. |
sinceoptional | instant, ISO 8601 | Only wins recorded at or after this instant (ISO 8601). |
Example response
{
"data": [
{
"account_id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"data": {
"caption": "Our autumn roast is here. Link in bio.",
"format": "reel",
"metric": "views",
"multiple": 4.7,
"post_id": "p4Hn8QzX2cVb7Lm1Rt6Wd",
"published_at": "2026-09-24T17:30:00.000Z",
"thumbnail_url": null,
"url": "https://www.instagram.com/reel/DcOX3hWFiey/",
"usual": 39100,
"value": 184000
},
"detected_at": "2026-09-26T07:10:12.000Z",
"id": "4812",
"kind": "post_spike"
}
],
"next_cursor": "4812"
}Start tracking an account
POST/api/v1/accounts
Start tracking a public profile in this workspace. It counts against the plan's account limit from now on, is read every day, and its first sync brings up to twelve months of posts and history. Answers the account, like GET /accounts/{account_id}: 201 when it was added or resumed, 200 when the workspace already tracked it (nothing changes, its folder included). Groowth waits up to 20 seconds on the network: a profile it could not read in time is tracked all the same, with status: "syncing", and fills in within minutes. Needs a key with write access. Not on the MCP server.
REST only, with a write key.
| Name | Type | Description |
|---|---|---|
folder_idbody, optional | string or null | File the account in this folder, by its id. Omitted or null: no folder. |
networkbody, required | one of instagram, youtube, tiktok, linkedin | The network the profile is on. A link names its own network; this one turns a bare handle into a profile. |
profilebody, required | string | The profile: its link, its handle or its @handle, like instagram.com/northwind.coffee, northwind.coffee or @northwind.coffee. On LinkedIn, the handle is what follows linkedin.com/in/. |
Example response
{
"added_at": "2026-09-29T08:02:41.000Z",
"avatar_url": "https://cdn.groowth.io/avatars/instagram/northwind.webp",
"backfill_pending": true,
"bio": "Slow-roasted coffee from Lyon.",
"changes": {
"d1": {
"followers": null,
"following": null
},
"d30": {
"followers": null,
"following": null
},
"d7": {
"followers": null,
"following": null
}
},
"display_name": "Northwind Coffee",
"folder_id": "Hq2mZ8Lw1TzVn4Ra9sKeX",
"handle": "northwind.coffee",
"id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"is_verified": false,
"latest": {
"date": "2026-09-29",
"engagement_rate": 3.4,
"followers": 48251,
"following": 412,
"posts_count": 961
},
"link": "https://northwind.example",
"network": "instagram",
"profile_url": "https://www.instagram.com/northwind.coffee/",
"status": "connected"
}Stop tracking an account
DELETE/api/v1/accounts/{account_id}
Stop tracking an account in this workspace. It leaves the plan's account limit and every list, and its goals are deleted. Its history is kept: tracking the same profile again brings it back, without the goals. Idempotent: an account this workspace already stopped answers 200 too. Needs a key with write access. Not on the MCP server.
REST only, with a write key. Answers even when the workspace is paused.
| Name | Type | Description |
|---|---|---|
account_idpath, required | string | The account's id in this workspace. |
Example response
{
"id": "t7Vd2QpL9xRz4Nw1Kb8Ys",
"tracked": false
}MCP server
The MCP server gives an AI assistant the same reads as the REST API, as tools. It speaks streamable HTTP at this address:
https://groowth.io/api/mcp- The same workspace key authenticates, sent as an
Authorization: Bearerheader. - Every tool is read-only, even with a write key: adding and stopping accounts is REST only. The tools share the key's rate limit with the REST API.
- Posts and wins come 20 per page by default, to keep the assistant's context small.
Any client that can send a fixed header can connect. Four common ones:
Claude Code
Run this once in a terminal, with your key in place of the placeholder:
claude mcp add --transport http groowth https://groowth.io/api/mcp --header "Authorization: Bearer groowth_sk_..."Cursor
Add the server to .cursor/mcp.json in a project, or to ~/.cursor/mcp.json for all of them:
{
"mcpServers": {
"groowth": {
"url": "https://groowth.io/api/mcp",
"headers": {
"Authorization": "Bearer groowth_sk_..."
}
}
}
}VS Code
Add it to .vscode/mcp.json. VS Code asks for the key when the server first starts, and stores it for you:
{
"inputs": [
{
"type": "promptString",
"id": "groowth-api-key",
"description": "Groowth API key",
"password": true
}
],
"servers": {
"groowth": {
"type": "http",
"url": "https://groowth.io/api/mcp",
"headers": {
"Authorization": "Bearer ${input:groowth-api-key}"
}
}
}
}Claude Desktop
Claude Desktop reaches a remote server through the mcp-remote bridge, which needs Node.js. Add this to claude_desktop_config.json:
{
"mcpServers": {
"groowth": {
"command": "npx",
"args": [
"mcp-remote",
"https://groowth.io/api/mcp",
"--header",
"Authorization:${GROOWTH_AUTH}"
],
"env": {
"GROOWTH_AUTH": "Bearer groowth_sk_..."
}
}
}
}Connectors that require an OAuth sign-in, like the web versions of Claude and ChatGPT, cannot connect yet.
Tools
get_workspace: Read the workspace this key belongs to.list_folders: List the workspace's folders.list_accounts: List the tracked accounts.get_account: Read one tracked account.list_account_snapshots: Read an account's daily history.list_posts: List posts.get_post: Read one post.list_goals: List publishing goals.list_wins: List wins.
Questions to try
- Which post did best against its account's usual this month?
- Who is behind on their posting goal this week?
- How many followers did each account of the Clients folder gain in 30 days?
Recipe: mirror posts into another tool
A common job: copy every post into another tool. For example, an analytics tool that marks each post on its traffic chart.
- Record the time each run starts.
- Call
GET /api/v1/postswithsort=recentandupdated_sinceset to the previous run's start, minus one hour. The overlap catches posts refreshed while that run was going. - Follow
next_cursoruntil it is null, with the other parameters unchanged. - Upsert each post by
id. The pairnetworkandnetwork_post_idworks too. - Re-list the accounts on each run. An account the workspace stops tracking disappears from every list, with no deletion event, so drop its posts on your side.
const API = "https://groowth.io/api/v1";
const HEADERS = { Authorization: "Bearer " + process.env.GROOWTH_API_KEY };
const HOUR = 60 * 60 * 1000;
export async function syncPosts(previousRunStart, upsertPost) {
const since = new Date(previousRunStart.getTime() - HOUR).toISOString();
let cursor = null;
while (true) {
const query = new URLSearchParams({
limit: "100",
sort: "recent",
updated_since: since,
});
if (cursor) query.set("cursor", cursor);
const response = await fetch(API + "/posts?" + query, { headers: HEADERS });
if (response.status === 429) {
const seconds = Number(response.headers.get("Retry-After") ?? "1");
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
continue;
}
if (!response.ok) throw new Error("Groowth API answered " + response.status);
const page = await response.json();
for (const post of page.data) await upsertPost(post);
if (page.next_cursor === null) return;
cursor = page.next_cursor;
}
}Details that matter
- On Instagram,
network_post_idis the numeric media id. The shortcode is inurl. thumbnail_urlis Groowth's own stable copy of the cover, on its CDN, usually within minutes of detection. Until then it may briefly be the network's signed URL, which expires within days. The copy moves the post'supdated_at, so your next run picks it up: keep the latestthumbnail_url, there is no image to copy. YouTube thumbnails are the network's own stable URLs and are not copied.published_atplaces a post on a timeline.detected_atis when Groowth first saw it, usually the next morning.- Recent posts are refreshed daily until their counters settle, so a later run updates them.
Wins, once a day
Wins are recorded once a day, around 9:00 in the workspace's time zone. Read GET /api/v1/wins with since once a day, after that hour.
OpenAPI document
The OpenAPI 3.0 document describes every endpoint, parameter and response schema on this page. Import it into an API client, or generate a typed client from it.
https://groowth.io/api/v1/openapi.jsonThe page, the document and the MCP tools are written from the same contract, so they cannot disagree.
Frequently asked questions
Is the API on every plan?
Yes. The REST API and the MCP server are on every plan, the free Solo plan included. What a key can read is what the workspace tracks, within its plan's quota.
Can the API post or add accounts?
It can add accounts, never post. A key the Owner creates with write access starts and stops tracking accounts through the REST API. Folders and goals are managed in the app, the MCP server stays read-only, and Groowth never publishes anything.
Which AI assistants can connect?
Any MCP client that can send a fixed Authorization header: Claude Code, Cursor and VS Code, and Claude Desktop through the mcp-remote bridge. Connectors that require an OAuth sign-in, like the web versions of Claude and ChatGPT, are not supported yet.