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.

API v1. Updated 2026-09-29

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

  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. Tick the box that lets it add and stop tracking accounts only if that tool manages the accounts. 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. 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:

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.

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:

Shell
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/v1 and 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 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 or a body field is missing, malformed or out of range, or the body is not JSON. details names it.
401unauthorizedThe key is missing, malformed, unknown or revoked. All four look the same on purpose.
403insufficient_scopeThe key can only read. Adding or stopping an account takes a key created with write access.
403workspace_pausedThe workspace is paused: its data is not served and no account can be added. GET /workspace and stopping an account still answer.
404not_foundNo such account, folder or post in this workspace, or it is no longer tracked.
409account_limit_reachedThe workspace already tracks as many accounts as its plan allows. Stop tracking one, or move to a larger plan.
422profile_refusedThe profile cannot be tracked, and trying again will not change that. reason says why: invalid_link, not_found, unsupported_network, unsupported_target.
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.
503provider_unavailableThe 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.

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

Every endpoint, and the MCP tool that serves the same read
EndpointMCP toolWhat it does
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
POST /api/v1/accountsREST onlyStart tracking an account
DELETE /api/v1/accounts/{account_id}REST onlyStop 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
JSON
{
  "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
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 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.

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 changed at or after this instant (ISO 8601): counters refreshed, or the thumbnail copied to Groowth's CDN.
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"
}

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.

Parameters
NameTypeDescription
folder_idbody, optionalstring or nullFile the account in this folder, by its id. Omitted or null: no folder.
networkbody, requiredone of instagram, youtube, tiktok, linkedinThe network the profile is on. A link names its own network; this one turns a bare handle into a profile.
profilebody, requiredstringThe 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
JSON
{
  "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.

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

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

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 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 signed URL, which expires within days. The copy moves the post's updated_at, so your next run 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.
  • 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?

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.