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
- 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.
- Pick only the scopes the key needs. The key is shown once, so copy it then.
- 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, optionalcursorin the query, string, optional- The
nextCursorof 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, optionaltitlein the body, string, requiredbodyin the body, string, required- The master copy, written for no platform in particular.
linkin the body, string (uri) or null, optionaltagsin 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, requiredcontentIdin the body, string, optional- Add to this draft's media. Needs
content:write. Give this orrole. 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(orcontent:write, whencontentIdis 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, optionalstatusin 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 livepost.failedA post was given up onpost.scheduledA post was scheduledpost.handed_offA post is due and waits for a person to post itcontent.createdA draft was createdcontent.approvedA person approved a pieceapproval.requestedA piece was sent for approval
Tools that read OpenAPI can load the same description from the OpenAPI document.