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.
What the Groowth API is
The Groowth API is a read-only REST API and MCP server for Groowth customers. It reads one workspace: its tracked accounts, their daily history, posts, posting goals and wins.
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 it does not do
- It does not write. It cannot add an account, change a goal or publish a post.
- It does not read any profile on demand. An account must be tracked in the workspace first.
- 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, 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 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.
Conventions
- Every endpoint lives under
/api/v1, answers a GET and returns JSON. - 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 is missing, malformed or out of range. details names it. |
| 401 | unauthorized | The key is missing, malformed, unknown or revoked. All four look the same on purpose. |
| 403 | workspace_paused | The workspace is paused, so its data is not served. GET /workspace still answers. |
| 404 | not_found | No such account, folder or post in this workspace, or it is no longer tracked. |
| 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. |
A paused workspace answers 403 workspace_paused everywhere except GET /workspace, which says why.
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.
| Endpoint | MCP tool | Reads |
|---|---|---|
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 |
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. 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"
},
"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 refreshed 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.
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 whose counters were refreshed at or after this instant (ISO 8601). |
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"
}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, and shares 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_urlmay be a signed network URL that expires within days. Copy the image if you keep it.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?
No. Version 1 is read-only: it reads what the workspace already tracks. Accounts, folders and goals are managed in the app, 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.