How to use the Metricool API: auth, scheduling posts, and the traps — VidVertex Resources

How to use the Metricool API: auth, scheduling posts, and the traps

Metricool has a real, working API for scheduling posts — and almost no documentation for it: one PDF and a help article that says to read IDs out of your browser's URL. We wired a desktop app to it, so here is the missing manual: the auth model, the scheduler payload that actually works, the media rule nobody tells you about, and the error that cost us two days.

Click to enlarge
Metricool account settings, API section: the REST API access token behind a Copy button, plus the account's API consumptionMetricool account settings, API section: the REST API access token behind a Copy button, plus the account's API consumption

Everything below was learned by building against the live API, not by paraphrasing the PDF. Where the official doc is silent, we say so — those parts are field-verified behavior, re-checked in September 2026 against posts that really published, and the usual caveat applies: undocumented behavior can change without notice.

Before you start: the plan and the three values

The API is a feature of Metricool's Advanced and Custom plans (no extra charge there — but on lower plans the token page simply doesn't exist). Every call uses up to three values:

Value Scope Where it rides
API token One per account The X-Mc-Auth header
User ID One per account A userId query parameter, on every call
Blog ID One per brand — a "brand" is a blog in API terms A blogId query parameter, on brand-level calls

If you don't have them yet: how to find your Metricool user ID, blog ID and API token.

The auth model

One header, one query parameter:

GET https://app.metricool.com/api/v2/settings/brands?userId=YOUR_USER_ID
X-Mc-Auth: YOUR_TOKEN

That's it — the token goes in the X-Mc-Auth header, userId rides as a query parameter on every call, and anything brand-level adds a blogId parameter next to it. The base URL is https://app.metricool.com/api.

TIP

That call is also the sanity check worth running first: it returns every brand on the account with its blog ID, display name and timezone.

Which networks a brand actually has lives in a networksData object on each brand entry — one key per connected network, the value being the connected account's name:

"networksData": { "instagramData": "founderhours", "tiktokData": "founderhours" }
WARNING

Guessing <network>Id keys matches nothing and makes every brand look network-less. Scheduling to a network the brand lacks is accepted, then fails at publish time.

Scheduling a post

The endpoint:

POST https://app.metricool.com/api/v2/scheduler/posts?blogId=BLOG_ID&userId=USER_ID
Content-Type: application/json
X-Mc-Auth: YOUR_TOKEN

One JSON object per post — a vertical video going to Instagram as a Reel:

{
  "providers": [{ "network": "instagram" }],
  "publicationDate": {
    "dateTime": "2026-09-01T18:30:00",
    "timezone": "Europe/Madrid"
  },
  "text": "Your caption goes here #withhashtags",
  "media": ["https://…/your-video.mp4"],
  "mediaAltText": [],
  "autoPublish": true,
  "draft": false,
  "firstCommentText": "First comment, posted right after.",
  "descendants": [],
  "hasNotReadNotes": false,
  "shortener": false,
  "smartLinkData": { "ids": [] },
  "instagramData": {
    "autoPublish": true,
    "type": "REEL",
    "showReelOnFeed": true,
    "tags": []
  }
}
WARNING

dateTime is planner-local wall-clock time — no UTC offset, no Z; the timezone field next to it names the zone it is local to. Compute times in UTC and paste them in, and your posts land at the wrong hour. And don't schedule in the past: Metricool rejects past-dated posts outright, where other schedulers quietly publish them immediately.

The rest of what the PDF won't tell you:

  • firstCommentText is a top-level field, and it only applies to the networks Metricool supports first comments on: Instagram, Facebook, LinkedIn, YouTube and TikTok. Sent to anything else it is ignored.
  • The tail of housekeeping fields (descendants, hasNotReadNotes, shortener, smartLinkData) is what Metricool's own planner UI sends. Include them empty rather than omitting them — you want your request to look like one the backend already knows.
  • Carousels are just multiple URLs in media with instagramData.type set to POST; stories are type: "STORY".

The per-network blocks

Each network in providers wants its own data block. The ones we've verified:

Network Block The fields that matter
Instagram instagramData type: REEL / POST / STORY, showReelOnFeed
Facebook facebookData type: REEL / STORY (Reels take their own title)
YouTube youtubeData title, type: "SHORT", privacy, category, madeForKids
TikTok tiktokData privacyOption: "PUBLIC_TO_EVERYONE", autoAddMusic, disableComment / Duet / Stitch
Pinterest pinterestData boardId (required in practice), pinTitle, pinLink
X (Twitter) twitterData tags
Bluesky blueskyData postLanguages
Threads threadsData allowedCountryCodes
NOTE

Two things from live use. YouTube wants a category — an enum like PEOPLE_BLOGS, GAMING or ENTERTAINMENT, the same list Metricool's bulk CSV template uses; posts without one tend to get dropped rather than rejected. And there is no Snapchat network anywhere in the API: Metricool simply doesn't have one.

The media rule: URLs only, and the normalize endpoint

There is no upload endpoint. The scheduler takes media only as public URLs — and URLs that aren't on Metricool's own hosts should be pushed through the normalize endpoint first, which downloads the file server-side and re-hosts it on Metricool infrastructure:

GET https://app.metricool.com/api/actions/normalize/image/url
    ?url=PUBLIC_MEDIA_URL&folder=PLANNER&userId=YOUR_USER_ID
X-Mc-Auth: YOUR_TOKEN
Accept: */*

Three surprises packed into one endpoint:

  • It takes videos. Ignore the /image/ in the path — it re-hosts video files just fine.
  • The response is plain text, not JSON: the body is the re-hosted URL. Check that it starts with http and use it as the media entry.
    TIP

    It is slow by design — the server downloads your file before it answers, so a rendered video behind a Google Drive share link legitimately takes a while. Give this one call a much longer read timeout than the rest of your client (we allow 300 seconds).

This is what makes a Google Drive public share link usable: normalize it, schedule the Metricool-hosted URL it returns — never the raw Drive link. And because Metricool now holds its own copy, the file on your Drive is dead weight the moment normalize answers.

Google Drive as the transfer vehicle: Metricool re-hosts the file and the Drive copy is deletedVidVertexDriveMetricoolre-hostspostshare linkthe Drive copy is deleted —Metricool has its own
FIG 1The media rule: Drive link in, Metricool-hosted URL out, and only that URL goes into the post. The Drive copy is dead weight afterwards.

Pictures must be JPEG or WebP

Videos are relaxed about format; pictures are not. A PNG comes back as:

The 'image/png' type is not allowed, use 'image/jpeg' or 'image/webp' instead

Instagram happened to swallow PNG carousels for a while; TikTok carousels did not, and a batch of them landed on ERROR without a word in the planner.

TIP

If your renderer writes PNG — most do, for the alpha — convert a JPEG twin before the upload, and flatten transparency onto white rather than onto the default black.

The error that cost us two days

If your HTTP client defaults to Accept: application/json — which every sensible API client does — the normalize endpoint answers:

{
  "status": "INTERNAL_SERVER_ERROR",
  "code": "500",
  "title": "InternalError",
  "detail": "No acceptable representation"
}

That's not a server outage and not your file: it's Spring content negotiation. The endpoint can only produce plain text, your header only accepts JSON, and the mismatch surfaces as HTTP 500 instead of the 406 that would have pointed at the header. Same call, same file — only the Accept header decides between 500 and 200.

TIP

Send Accept: */* on the normalize call — and log every failed request's URL and response body from day one. Metricool's error bodies are terse, and without logs this one looks exactly like a broken video file.

Reading the calendar back

GET https://app.metricool.com/api/v2/scheduler/posts
    ?blogId=BLOG_ID&userId=USER_ID
    &start=2026-09-01T00:00:00&end=2026-09-30T23:59:59
    &timezone=UTC&extendedRange=false

This lists the brand's posts in the window — scheduled ones and past ones alike. The status is not on the post: it sits on each entry of the post's providers array as PENDING, PUBLISHING, PUBLISHED or ERROR, with a detailedStatus string next to it.

WARNING

Read that array, or every post in your calendar reads "scheduled" forever — including the failed ones. An ERROR entry that still carries a publicUrl is the third case: the post went live, but something on it (a first comment, usually) did not.

Returned publicationDate.dateTime values are planner-local, same as when you sent them — no conversion on the way back either.

Retry rules that won't double-post

Worth stating because the failure modes differ per endpoint:

Reads and normalize are safe to retry — on network errors and on transient statuses (408, 429, 5xx), with a short backoff.

WARNING

The scheduling POST is not idempotent. Retry it only when the connection failed outright and the request provably never reached the server — a blind retry on a timeout can double-post.

Where VidVertex fits

This page is what VidVertex's Metricool API upload mode does per rendered variant — live since September 2026, next to the older Metricool (Drive + CSV) mode that writes an import file instead (upload modes). Paste token and user ID once in Settings → Upload settings; the brand editor lists your brands over this very endpoint, so each one picks its blog ID from a dropdown, and pre-flight warns when a brand's channel isn't among that blog's connected networks.

Click to enlarge
VidVertex Settings → Upload settings, Metricool API tab: API token, user ID, the Drive credentials and the Google Drive connection showing ConnectedVidVertex Settings → Upload settings, Metricool API tab: API token, user ID, the Drive credentials and the Google Drive connection showing Connected
FIG 2Settings, Upload settings: token and user ID pasted once, and each brand picks its blog ID from a dropdown the app fills from your Metricool account.

The rest is plumbing: media goes to your Google Drive as a public link, through normalize, and the Drive copy is deleted once Metricool has re-hosted it; pictures are converted to JPEG first; times are computed in each brand's own timezone, so nothing lands in the past.

NOTE

Two things the API cannot carry are gaps, not features: covers are not sent (there is no verified payload field for them), and Metricool has no Snapchat network, so Snapchat channels are skipped in both Metricool modes.

Ready to multiply your output?

VidVertex runs on your Windows machine — your content, your accounts, no per-post fees. One subscription covers every brand, platform and language you run.