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.

API v1, read-only. Updated 2026-09-29

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

  1. Open Settings > API in the workspace you want to read. Only the Owner sees this page.
  2. Name the key after the tool that will use it, then create it.
  3. 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.

HTTP
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:

Shell
export GROOWTH_API_KEY="groowth_sk_..."

Check the key. This call returns the workspace it belongs to:

Shell
curl https://groowth.io/api/v1/workspace \
  -H "Authorization: Bearer $GROOWTH_API_KEY"

List the five latest Instagram posts of the workspace:

Shell
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 or Z.
  • 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, never 0.

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:

HeaderMeaning
X-RateLimit-LimitRequests allowed per window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window starts over.
Retry-AfterOn 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:

JSON
{
  "error": {
    "code": "invalid_request",
    "details": [
      {
        "message": "Pass `account_id` or `folder_id`, not both.",
        "path": "folder_id"
      }
    ],
    "message": "Invalid request."
  }
}
StatusCodeMeaning
400invalid_requestA parameter is missing, malformed or out of range. details names it.
401unauthorizedThe key is missing, malformed, unknown or revoked. All four look the same on purpose.
403workspace_pausedThe workspace is paused, so its data is not served. GET /workspace still answers.
404not_foundNo such account, folder or post in this workspace, or it is no longer tracked.
429rate_limitedMore than 120 requests in 60 seconds with this key. Wait for Retry-After seconds.
500internalSomething 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.

Every endpoint, and the MCP tool that serves the same read
EndpointMCP toolReads
GET /api/v1/workspaceget_workspaceRead the workspace this key belongs to
GET /api/v1/folderslist_foldersList the workspace's folders
GET /api/v1/accountslist_accountsList the tracked accounts
GET /api/v1/accounts/{account_id}get_accountRead one tracked account
GET /api/v1/accounts/{account_id}/snapshotslist_account_snapshotsRead an account's daily history
GET /api/v1/postslist_postsList posts
GET /api/v1/posts/{post_id}get_postRead one post
GET /api/v1/goalslist_goalsList publishing goals
GET /api/v1/winslist_winsList 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
JSON
{
  "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
JSON
{
  "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.

Parameters
NameTypeDescription
folder_idoptionalstringOnly the accounts of this folder.
networkoptionalone of instagram, youtube, tiktok, linkedinOnly this network.
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
account_idpath, requiredstringThe account's id in this workspace.
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
account_idpath, requiredstringThe account's id in this workspace.
fromoptionalday, YYYY-MM-DDFirst day, included. Defaults to 90 days before to.
tooptionalday, YYYY-MM-DDLast day, included. Defaults to today (UTC).
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
account_idoptionalstringOnly this account's posts. Not with folder_id.
cursoroptionalstringThe next_cursor of the previous page.
folder_idoptionalstringOnly the posts of this folder's accounts. Not with account_id.
limitoptionalinteger, 1 to 100Posts per page, 1 to 100.
networkoptionalone of instagram, youtube, tiktok, linkedinOnly this network.
published_afteroptionalinstant, ISO 8601Published at or after this instant (ISO 8601).
published_beforeoptionalinstant, ISO 8601Published strictly before this instant (ISO 8601).
sortoptionalone of recent, likes, comments, performance, default recentrecent (default, newest first: the stable order to page through everything), performance (best against the account's usual first), likes or comments.
updated_sinceoptionalinstant, ISO 8601Only posts whose counters were refreshed at or after this instant (ISO 8601).
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
post_idpath, requiredstringThe post's Groowth id (id in list_posts).
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
account_idoptionalstringOnly this account's goals.
Example response
JSON
{
  "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.

Parameters
NameTypeDescription
cursoroptionalstringThe next_cursor of the previous page.
kindoptionalone of goal_hit, post_spike, follower_spikeOnly this kind of win.
limitoptionalinteger, 1 to 100Wins per page, 1 to 100.
sinceoptionalinstant, ISO 8601Only wins recorded at or after this instant (ISO 8601).
Example response
JSON
{
  "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:

URL
https://groowth.io/api/mcp
  • The same workspace key authenticates, sent as an Authorization: Bearer header.
  • 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:

Shell
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:

JSON
{
  "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:

JSON
{
  "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:

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

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.

  1. Record the time each run starts.
  2. Call GET /api/v1/posts with sort=recent and updated_since set to the previous run's start, minus one hour. The overlap catches posts refreshed while that run was going.
  3. Follow next_cursor until it is null, with the other parameters unchanged.
  4. Upsert each post by id. The pair network and network_post_id works too.
  5. 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.
JavaScript
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_id is the numeric media id. The shortcode is in url.
  • thumbnail_url may be a signed network URL that expires within days. Copy the image if you keep it.
  • published_at places a post on a timeline. detected_at is 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.

URL
https://groowth.io/api/v1/openapi.json

The 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.