For agents

Agent recipes

Copyable end-to-end sequences — create, validate, and schedule a cross-platform post in one call, then pull analytics — using real Inklate tools and endpoints.

Two flows an agent can follow literally. Each is shown as an ordered sequence of MCP tool calls, with the equivalent REST path beside it. Names and arguments are exactly those the API exposes — do not invent others.

Recipe A — one call from nothing to a scheduled cross-platform post

The goal: one post published natively to a LinkedIn page and an X account, validated before it ships, scheduled for a future instant.

1. Find the channels

Call channels_list (REST: GET /channels) to get channel ids and connection health. Optionally call capabilities (GET /capabilities) to confirm each target can publish right now and to read its text limits. If the organization has exactly one channel per provider, you can skip this — channels accepts provider names ("x", "linkedin") and resolves them server-side.

2. Create and schedule — one call

Call posts_create (REST: POST /posts) with the content, the target channels, and the schedule:

{
  "content": "We just closed our Series A — here's what's next.",
  "channels": ["chn_linkedin_page", "x"],
  "scheduled_at": "2026-07-21T09:00:00-07:00",
  "idempotency_key": "series-a-announce-1"
}

content is markdown, and it is the only way to write copy — one field, one form, on every door. Copy with no markup posts exactly as typed, so handles, URLs and snake_case survive verbatim; when a literal delimiter has to appear, escape it (\*\*not bold\*\*). Reads answer with the same string in content.markdown, so a response is a legal request: read it, edit it, send it back.

Your content fans out into a copy per channel automatically; per_channel overrides one channel’s copy or format when you want to tailor it (an X thread beside a LinkedIn post, say):

{ "per_channel": { "x": { "format": "thread" } } }

The response carries everything: the post id, top-level ready, and a channels[] entry per target with its adapted copy (counted length against the provider limit), errors[], warnings[], and any fixes_appliedautofix (default on) repairs what it safely can, server-side, in the same call.

3. If the schedule was refused

Arming is atomic: if any channel isn’t ready, the scheduled write is refused (400, reason: "not_ready") and nothing is saved. The refusal answers with the post it refused — error.post over REST, JSON below the error line over MCP — so read errors[] straight off the call that failed instead of re-sending the write without scheduled_at. Each error names a code, the field to change, a message with the real numbers, and machine fixes where a repair exists — see Errors and fixes for the catalog. Apply a fix by writing the repaired content (or a new format) via posts_update / PATCH /posts/{id} with per_channel, or simply leave autofix on and let the server take the safe repairs.

To check without saving anything, posts_validate (REST: POST /posts/validate) is the dry run — pass the same inline draft, or a stored post’s id to re-check exactly what is saved, media readiness included.

4. Reschedule or change anything

posts_update (REST: PATCH /posts/{id}) changes content, channels, media, labels, campaign — and the schedule: a new scheduled_at time rebooks it, "now" publishes it, null returns it to draft. Every update re-validates and returns the same response shape. Pass expected_updated_at from your last read to make concurrent edits refuse instead of clobber.

Editing the shared content never rewrites a channel you tailored — that is the whole point of per_channel. When you do want an edit pushed out, name the channels:

{ "per_channel": { "x": { "copy_from": { "channel": "shared" } } } }

To tailor one channel outright, read its content.markdown, edit the string, and send it back as per_channel: { "x": { "content": "…" } } — the same form in both directions. channel can also name another channel this post targets ("linkedin", or a channel id) to reuse its wording, and text / media scope which halves are overwritten (both default to true). per_channel: { "x": null } is the blunt version: reset that channel to the adapted shared content. An unknown key inside a per_channel entry is a 400 — nothing you send is silently ignored.

5. Confirm it shipped

After the scheduled instant, poll posts_get (REST: GET /posts/{id}) — there is no separate status call. Each channel entry fills in its publish result: status, external_post_id, the live url, or a sanitized error. To publish immediately instead of scheduling, either send "scheduled_at": "now" on create, or call posts_publish (REST: POST /posts/{id}/publish) on an existing draft with an idempotency_key so retries replay safely.

Recipe B — pull analytics for recent posts

The goal: gather engagement for the posts you published lately.

1. List recent published posts

Call posts_list (REST: GET /posts) filtered to status = published and, optionally, a scheduled-time range. Narrow further with campaign, label, or channel (a channel id or a provider name like linkedin) rather than pulling a range down and filtering locally. This returns { items, next_cursor } — posts with their per-channel targets and ids. It is keyset-paginated on both doors: pass next_cursor back as cursor for the following page, and limit to size it.

Rows are excerpts and carry no fit verdict by default, because a calendar range must not run a fit check per row. When the question is “which of these can I publish?”, ask for the field: include: ["ready"] (REST: ?include=ready) adds ready per row — the same verdict posts_get reports. That page is capped at 25 rows, since each judged row costs a full post read; page through next_cursor for more. An absent ready means the read never checked, which is not the same as false.

2. Pull per-post metrics

For each post id, call posts_metrics (REST: GET /posts/{id}/metrics). You get per-channel totals — impressions, reactions, comments, shares, clicks — plus the full snapshot time series for that post.

3. Or roll up by channel

To see a channel’s aggregate performance rather than one post’s, call channels_metrics (REST: GET /channels/{id}/metrics) for per-day engagement across that channel’s published posts, optionally restricted to a time range.

Metrics are synced from each platform in the background, not on every read. A freshly published post may show zero until the first sync lands. See Analytics syncing for how and when snapshots refresh.

Notes for agents

  • Absolute timestamps always carry an offset. Send 2026-07-21T09:00:00-07:00 or a Z instant. Naive local times are rejected with a corrective error. The scheduled_at field also accepts 30m/2h/3d/1w shorthands, anchored to the server clock.
  • Idempotency. Pass an idempotency_key (MCP) or Idempotency-Key header (REST) on posts_create and posts_publish so a retried call replays instead of duplicating.
  • Approvals. When the organization requires review, a post arms only once approved. Over MCP, approvals_rule reads the org’s rule (so you know whether the gate applies), approvals_request submits a post for review, and approvals_decide records an approver’s verdict — only users the rule names as approvers may call it.
  • One call, not a saga. There is no separate workflow surface to start and poll. posts_create already validates every target, applies fixes, and arms the schedule or the immediate publish in one transaction — so a refused arm leaves nothing behind and there is nothing for you to compensate.