Skip to main content

Sheevook API reference

Read a project's content, posts and analytics, add drafts, and propose a publish time. 11 endpoints, one key per project, JSON in and out.

What a key can never do

A key can add drafts, read analytics and propose a time. It can never approve a post, schedule it or publish it. A person does those in the app. Sending a draft for review is the furthest a key goes, and the person who reviews it decides what happens next.

The inbox (messages and reviews) is not in the API. Replies go through people.

Authentication

  1. Open Integrations in Sheevook, find API keys, and make a key for the project. You need to be an owner or an admin of the workspace.
  2. Pick only the scopes the key needs. The key is shown once, so copy it then.
  3. Send it on every request as Authorization: Bearer shv_.... A key in a query string is never read.

A key belongs to one project and stops working when the person who made it is removed or loses the admin role. Call it from your server: there is no CORS.

content:readRead content
List drafts and posts' master copy and see each platform version's status.
content:writeCreate drafts
Add a draft, or propose a time for a piece. It still needs a person to approve and schedule it.
posts:readRead posts
List published, scheduled and failed posts.
analytics:readRead analytics
See how published posts performed, per post and per platform, including leads, conversions and attributed revenue.
webhooks:writeManage webhooks
Add and remove the webhooks that hear about posts and content: a post going live, failing, being scheduled or waiting for a person, and a piece being created, sent for approval or approved.
media:writeAdd images
Bring an image in from an https address into the brand library, or onto a draft. It is never attached to a post that is scheduled or approved.

Errors and limits

An error is a JSON body with a message, { "error": "..." }, and a details field that names the failing fields when a request does not validate. It is not an RFC 9457 problem document.

A missing, unknown, revoked or expired key is always the same 401, so the answer never says which check refused. A key without the scope an endpoint needs is a 403. Content from another project is a 404.

Each key may make 120 requests a minute. Past that the answer is a 429 with a Retry-After header giving the seconds to wait.

Dates are ISO 8601 in UTC. Money is whole cents. Answers are never cached, and every list is bounded.

Account

GET/api/v1/me

The project and key this request is for

Any live key may call it.

Any live key may call it. It is the cheapest way to check a key still works, and the project name is a good label for a connection.

Example request

curl "https://www.sheevook.com/api/v1/me" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 200

{
  "project": {
    "id": "cnt_8f2c1a",
    "name": "Zapier"
  },
  "key": {
    "id": "cnt_8f2c1a",
    "name": "Zapier",
    "prefix": "shv_a1b2c3d4",
    "scopes": [
      "content:read"
    ],
    "createdAt": "2026-11-02T09:00:00Z",
    "lastUsedAt": "2026-11-02T09:00:00Z",
    "expiresAt": "2026-11-02T09:00:00Z"
  }
}

Errors

  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 429More than 120 requests a minute for this key.

GET/api/v1/openapi.json

This document

Needs no key.

Needs no key.

Example request

curl "https://www.sheevook.com/api/v1/openapi.json"

Example response, 200

No body.

Analytics

GET/api/v1/analytics

How published posts performed in a date range

Needs the analytics:read scope.

Needs analytics:read. Per-platform totals and per-post figures for posts published inside the range. The range is at most 92 days: to defaults to now and from to 30 days before to. A longer range is a 400.

Every figure is { value, tier }. The tier is input (what was done), output (a proxy: the post landed, which is not the same as it paying off) or outcome (what it was worth). Impressions, engagements and clicks are outputs; do not rank on them as if they were results. measures in the response says what each figure counts. value is null when no platform has reported that figure: a post nobody measured is not a post that got nothing.

Totals and platforms cover every post in the range. posts lists the newest limit (at most 200) and postCount says how many there were. Money is in integer cents in currency. No lead or contact data is returned, only counts.

Parameters

fromin the query, string (date-time), optional
Start of the range, ISO 8601 UTC (ends in Z). Inclusive.
toin the query, string (date-time), optional
End of the range, ISO 8601 UTC. Inclusive. Defaults to now.
limitin the query, integer, optional
How many posts to list.

Example request

curl "https://www.sheevook.com/api/v1/analytics?from=2026-11-02T09%3A00%3A00Z&to=2026-11-02T09%3A00%3A00Z&limit=50" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 200

{
  "range": {
    "from": "2026-11-02T09:00:00Z",
    "to": "2026-11-02T09:00:00Z"
  },
  "currency": "USD",
  "totals": {
    "posts": {
      "value": 1,
      "tier": "input"
    },
    "measuredPosts": {
      "value": 1,
      "tier": "input"
    },
    "impressions": {
      "value": 1,
      "tier": "input"
    },
    "engagements": {
      "value": 1,
      "tier": "input"
    },
    "engagementRatePct": {
      "value": 1,
      "tier": "input"
    },
    "platformClicks": {
      "value": 1,
      "tier": "input"
    },
    "ownedClicks": {
      "value": 1,
      "tier": "input"
    },
    "leads": {
      "value": 1,
      "tier": "input"
    },
    "conversions": {
      "value": 1,
      "tier": "input"
    },
    "revenueCents": {
      "value": 1,
      "tier": "input"
    }
  },
  "platforms": [
    {
      "platform": "twitter",
      "name": "Zapier",
      "posts": {
        "value": 1,
        "tier": "input"
      },
      "measuredPosts": {
        "value": 1,
        "tier": "input"
      },
      "impressions": {
        "value": 1,
        "tier": "input"
      },
      "engagements": {
        "value": 1,
        "tier": "input"
      },
      "engagementRatePct": {
        "value": 1,
        "tier": "input"
      },
      "platformClicks": {
        "value": 1,
        "tier": "input"
      },
      "ownedClicks": {
        "value": 1,
        "tier": "input"
      },
      "leads": {
        "value": 1,
        "tier": "input"
      },
      "conversions": {
        "value": 1,
        "tier": "input"
      },
      "revenueCents": {
        "value": 1,
        "tier": "input"
      }
    }
  ],
  "posts": [
    {
      "variantId": "var_4d7e90",
      "contentId": "cnt_8f2c1a",
      "platform": "twitter",
      "title": "Spring launch announcement",
      "format": "Single post",
      "publishedAt": "2026-11-02T09:00:00Z",
      "measured": false,
      "impressions": {
        "value": 1,
        "tier": "input"
      },
      "engagements": {
        "value": 1,
        "tier": "input"
      },
      "engagementRatePct": {
        "value": 1,
        "tier": "input"
      },
      "platformClicks": {
        "value": 1,
        "tier": "input"
      },
      "ownedClicks": {
        "value": 1,
        "tier": "input"
      },
      "leads": {
        "value": 1,
        "tier": "input"
      },
      "conversions": {
        "value": 1,
        "tier": "input"
      },
      "revenueCents": {
        "value": 1,
        "tier": "input"
      }
    }
  ],
  "postCount": 1,
  "measures": {
    "impressions": {
      "label": "Spring range",
      "tier": "input",
      "source": "recorded",
      "note": "Aim for Friday morning, before the email goes out."
    }
  }
}

Errors

  • 400A bad date, an empty range, or a range over 92 days.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks analytics:read.
  • 429More than 120 requests a minute for this key.

Content

GET/api/v1/content

List the project's content, newest first

Needs the content:read scope.

Needs content:read. The master copy is left out of the list; get one piece for it.

Parameters

limitin the query, integer, optional
cursorin the query, string, optional
The nextCursor of the previous page.

Example request

curl "https://www.sheevook.com/api/v1/content?limit=25&cursor=cursor" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 200

{
  "data": [
    {
      "id": "cnt_8f2c1a",
      "title": "Spring launch announcement",
      "status": "draft",
      "link": "https://example.com/spring",
      "tags": [
        "tags"
      ],
      "createdAt": "2026-11-02T09:00:00Z",
      "updatedAt": "2026-11-02T09:00:00Z"
    }
  ],
  "nextCursor": "nextCursor"
}

Errors

  • 400A bad limit or cursor.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks content:read.
  • 429More than 120 requests a minute for this key.

POST/api/v1/content

Add a draft

Needs the content:write scope.

Needs content:write. Creates a DRAFT master piece. It is never approved, scheduled, tailored or published by this call; a person reviews it in the app. A field the schema does not list is a 400.

Send an Idempotency-Key header and a retry cannot make a second draft: the same key and body returns the first answer with Idempotent-Replayed: true; the same key with a different body is a 422. Keys are kept 24 hours, per API key.

Parameters

Idempotency-Keyin the header, string, optional
titlein the body, string, required
bodyin the body, string, required
The master copy, written for no platform in particular.
linkin the body, string (uri) or null, optional
tagsin the body, array of string, optional

Example request

curl -X POST "https://www.sheevook.com/api/v1/content" \
  -H "Authorization: Bearer shv_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Spring launch announcement","body":"We are opening the spring range on Friday. Details at the link.","link":"https://example.com/spring","tags":["tags"]}'

Example response, 201

{
  "id": "cnt_8f2c1a",
  "title": "Spring launch announcement",
  "status": "draft",
  "link": "https://example.com/spring",
  "tags": [
    "tags"
  ],
  "createdAt": "2026-11-02T09:00:00Z",
  "updatedAt": "2026-11-02T09:00:00Z",
  "body": "We are opening the spring range on Friday. Details at the link."
}

Errors

  • 400The body failed validation.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks content:write.
  • 422The Idempotency-Key was already used with a different body.
  • 429More than 120 requests a minute for this key.

GET/api/v1/content/{id}

One piece with its platform versions

Needs the content:read scope.

Needs content:read. Content in another project is a 404.

Parameters

idin the path, string, required
The content id.

Example request

curl "https://www.sheevook.com/api/v1/content/YOUR_ID" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 200

{
  "id": "cnt_8f2c1a",
  "title": "Spring launch announcement",
  "status": "draft",
  "link": "https://example.com/spring",
  "tags": [
    "tags"
  ],
  "createdAt": "2026-11-02T09:00:00Z",
  "updatedAt": "2026-11-02T09:00:00Z",
  "body": "We are opening the spring range on Friday. Details at the link.",
  "variants": [
    {
      "id": "cnt_8f2c1a",
      "platform": "twitter",
      "status": "status",
      "scheduledAt": "2026-11-02T09:00:00Z",
      "publishedAt": "2026-11-02T09:00:00Z"
    }
  ]
}

Errors

  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks content:read.
  • 404No such content in this project.
  • 429More than 120 requests a minute for this key.

POST/api/v1/content/{id}/schedule-request

Propose a publish time for a piece

Needs the content:write scope.

Needs content:write. This NEVER schedules: no platform version gets a time, nothing is queued, and the piece does not become scheduled or approved. A person approves it and picks the time in the app.

The proposal is recorded as an internal comment on the piece, where the team reviews it (a client's share link never shows it). A draft is also sent for review, the way the Submit for review button does it, with the time in the request's note. A draft with no platform versions cannot be sent for review by anyone, so it stays a draft: the proposal is still recorded, submittedForReview is false and reviewBlockedReason says why. A piece already in review or approved only gets the comment. A scheduled or published piece is a 409.

requestedAt is ISO 8601 UTC (ends in Z), in the future and at most 365 days ahead. There is no Idempotency-Key here: a retry adds another comment.

Parameters

idin the path, string, required
The content id.
requestedAtin the body, string (date-time), required
ISO 8601 UTC (ends in Z), in the future, at most 365 days ahead.
notein the body, string, optional
A word for the reviewer.

Example request

curl -X POST "https://www.sheevook.com/api/v1/content/YOUR_ID/schedule-request" \
  -H "Authorization: Bearer shv_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requestedAt":"2027-03-01T14:00:00Z","note":"Aim for Friday morning, before the email goes out."}'

Example response, 201

{
  "content": {
    "id": "cnt_8f2c1a",
    "title": "Spring launch announcement",
    "status": "draft",
    "link": "https://example.com/spring",
    "tags": [
      "tags"
    ],
    "createdAt": "2026-11-02T09:00:00Z",
    "updatedAt": "2026-11-02T09:00:00Z",
    "body": "We are opening the spring range on Friday. Details at the link."
  },
  "request": {
    "requestedAt": "2027-03-01T14:00:00Z",
    "note": "Aim for Friday morning, before the email goes out."
  },
  "submittedForReview": false,
  "reviewBlockedReason": "reviewBlockedReason"
}

Errors

  • 400The body failed validation, or the time is in the past or more than a year ahead.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks content:write.
  • 404No such content in this project.
  • 409The piece is already scheduled or published.
  • 429More than 120 requests a minute for this key.

Media

POST/api/v1/media

Bring an image in from an https address

Needs the media:write scope.

Needs media:write. Sheevook downloads the image itself, from a public https address (no credentials, no port; every redirect is checked again), up to 10 MB for the brand library or 100 MB for a draft, and reads the type from the file, not from what the server called it. PNG, JPEG, WebP and GIF only.

Name exactly one destination: role puts it in the brand library; contentId puts it with a draft's media and also needs content:write. It never joins a platform version, so nothing already approved or scheduled changes; a person attaches it. Alt text is required.

The file is recorded as not AI-made unless you send aiGenerated: true. Send it for AI-generated art: platforms that label AI media (TikTok, YouTube, Instagram, Facebook, X) are told what is recorded when a post using the file is published. There is no Idempotency-Key here: a retry imports the file again.

Parameters

urlin the body, string (uri), required
https, on a public server.
altTextin the body, string, required
contentIdin the body, string, optional
Add to this draft's media. Needs content:write. Give this or role.
rolein the body, "logo" | "product" | "team" | "screenshot" | "other", optional
Add to the brand library as this. Give this or contentId.
labelin the body, string, optional
A library image's name. Only with role.
aiGeneratedin the body, boolean, optional
True when the image was made with AI. Platforms that label AI media are told so when a post using it is published.

Example request

curl -X POST "https://www.sheevook.com/api/v1/media" \
  -H "Authorization: Bearer shv_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/sheevook","altText":"A shop window with the spring range on display","role":"logo"}'

Example response, 201

{
  "asset": {
    "id": "cnt_8f2c1a",
    "contentId": "cnt_8f2c1a",
    "role": "logo",
    "label": "Spring range",
    "altText": "A shop window with the spring range on display",
    "mimeType": "image/png",
    "byteSize": 1,
    "width": 1,
    "height": 1,
    "createdAt": "2026-11-02T09:00:00Z"
  }
}

Errors

  • 400The body failed validation, or the draft can no longer take images.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks media:write (or content:write, when contentId is named).
  • 404No such content in this project.
  • 413The file is over the size limit.
  • 422The address is not one Sheevook will download from, the image host refused it, has no file there or answered another 4xx such as 429 (the message names the host's HTTP status; the URL must open without a login), or the file is not an image it can use.
  • 429Too many imports for this project in a minute.
  • 502The image host failed to send the file (it answered a 5xx).
  • 504The image host did not answer in time.

Posts

GET/api/v1/posts

List posts in one status, newest first

Needs the posts:read scope.

Needs posts:read. Ordered by when the post went live, gave up, or is due. Each read is bounded in time: published and failed posts from the last 90 days, scheduled posts from 30 days overdue to a year ahead.

post is the same object a webhook delivery carries, including url, the live address the platform gave when the post went out. url is null for a post that is not published, and for a published post whose platform gave none (or that went out before the address was kept).

Parameters

limitin the query, integer, optional
statusin the query, "published" | "scheduled" | "failed", optional

Example request

curl "https://www.sheevook.com/api/v1/posts?limit=25&status=published" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 200

{
  "data": [
    {
      "id": "cnt_8f2c1a",
      "status": "published",
      "scheduledAt": "2026-11-02T09:00:00Z",
      "publishedAt": "2026-11-02T09:00:00Z",
      "post": {
        "contentId": "cnt_8f2c1a",
        "variantId": "var_4d7e90",
        "platform": "twitter",
        "title": "Spring launch announcement",
        "url": "https://example.com/hooks/sheevook",
        "error": "The platform said no.",
        "scheduledAt": "2026-11-02T09:00:00Z"
      }
    }
  ]
}

Errors

  • 400A bad status or limit.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks posts:read.
  • 429More than 120 requests a minute for this key.

Webhooks

POST/api/v1/webhooks

Subscribe a URL to post and content events

Needs the webhooks:write scope.

Needs webhooks:write. The address must be https on a public server. A project can have up to 20 webhooks made through the API, counted apart from the destinations added in the app; a full project answers 409.

The signing secret is in the response once. Revoking the key deletes the webhooks it made.

Each delivery is a POST of a JSON body with the headers X-Sheevook-Event (one of the event names below), X-Sheevook-Delivery (the event id, also id in the body), X-Sheevook-Timestamp (unix seconds) and X-Sheevook-Signature (sha256= and the lowercase hex HMAC-SHA256, keyed by the secret returned when the webhook was created, of the string <timestamp>.<raw body>). Compare in constant time and refuse a timestamp more than a few minutes from your clock. Deliveries are attempted once and not retried.

Parameters

urlin the body, string (uri), required
https, on a public server.
eventsin the body, array of "post.published" | "post.failed" | "post.scheduled" | "post.handed_off" | "content.created" | "content.approved" | "approval.requested", required

Example request

curl -X POST "https://www.sheevook.com/api/v1/webhooks" \
  -H "Authorization: Bearer shv_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/sheevook","events":["post.published"]}'

Example response, 201

{
  "id": "cnt_8f2c1a",
  "events": [
    "post.published"
  ],
  "secret": "whsec_example_shown_once"
}

Errors

  • 400The body failed validation, or the address is not a public https server.
  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks webhooks:write.
  • 409The project has all the API webhooks it may have, or one is being added at this moment.
  • 429More than 120 requests a minute for this key.

DELETE/api/v1/webhooks/{id}

Unsubscribe a webhook

Needs the webhooks:write scope.

Needs webhooks:write. Removes a webhook this key made. Any other id, including a destination added in the app or a webhook another key made, is a 404.

Parameters

idin the path, string, required
The webhook id.

Example request

curl -X DELETE "https://www.sheevook.com/api/v1/webhooks/YOUR_ID" \
  -H "Authorization: Bearer shv_YOUR_KEY"

Example response, 204

No body.

Errors

  • 401The key is missing, malformed, unknown, revoked, expired, or its creator can no longer manage the workspace. The answer is the same for every cause.
  • 403The key lacks webhooks:write.
  • 404No such webhook made by this key.
  • 429More than 120 requests a minute for this key.

Webhook events

Subscribe a URL with POST /api/v1/webhooks and each of these is sent to it, signed. Hearing an event never lets a key act on it.

  • post.publishedA post went live
  • post.failedA post was given up on
  • post.scheduledA post was scheduled
  • post.handed_offA post is due and waits for a person to post it
  • content.createdA draft was created
  • content.approvedA person approved a piece
  • approval.requestedA piece was sent for approval

Tools that read OpenAPI can load the same description from the OpenAPI document.