REST · v1
Surgr API reference
Give your agent a clear contract: the fields it can send, the permissions it needs and the results it can read.
https://surgr.ai/api/v1Send Authorization: Bearer <key>. Keep the key in your own SURGR_API_KEY environment variable.
Keys are scoped by the permissions you tick for that connection. A missing permission returns 403. Reads still require a valid key and apply workspace and platform scope.
On this page
Create a connection
Open Agents after signing in.
- Sign in to Surgr, open Agents and choose Connect agent.
- Fill Connection name. Under Access, choose Prepare for review (recommended), or tick the exact permissions you intend to grant.
- Tick the connected platforms under Allowed accounts. Choose Create connection.
- Choose Copy key under Copy this key now. It is shown once; store it securely and expose it to your own terminal as SURGR_API_KEY.
- Optionally choose Test read-only connection, then Done. The same key works for REST and MCP.
The default Prepare for review grants draft. Draft and schedule also grants schedule and manage_own; scheduling records the agent’s approval. Choose permissions deliberately.
Post a video from an agent in 5 calls
The five calls below assume an explicitly trusted Draft and schedule connection (draft, schedule, manage_own), curl, jq and a local demo.mp4. Step 5 records approval by that connection. For human review, keep Prepare for review, set requestReview: true at step 4, and stop there: open the Inbox, check every platform preview and choose Approve & schedule. That web action already schedules the post. Do not run step 5 with a review-only key or treat requestReview as approval.
1Presign the video
Choose your local video and exact byte count. This response reserves media in processing state; the upload URL expires in 300 seconds.
VIDEO_BYTES=$(wc -c < ./demo.mp4 | tr -d ' ')
curl --fail-with-body --silent --show-error -X POST https://surgr.ai/api/v1/media/presign \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"fileName\":\"demo.mp4\",\"fileType\":\"video/mp4\",\"fileSize\":$VIDEO_BYTES}" \
-o presign.json2Upload to the presigned URL
PUT the same file and MIME type directly to uploadUrl. Do not send your Surgr bearer key to the upload host. Stop if the upload fails.
UPLOAD_URL=$(jq -r '.uploadUrl' presign.json)
curl --fail-with-body --silent --show-error -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
--upload-file ./demo.mp43Confirm the media
Confirm media.id after the PUT succeeds. The handler verifies stored size and MIME type before marking it ready.
MEDIA_ID=$(jq -r '.media.id' presign.json)
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/media/$MEDIA_ID/confirm" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-o media.json4Create the video post
Use the ready media ID, targetPlatforms and platformText. Replace the example future time; connect and allow both X and YouTube. For the recommended human-review flow, set requestReview to true and stop after this call.
SCHEDULED_AT='2030-01-02T09:00:00Z' # Replace with a real future time.
jq -n --arg media "$(jq -r '.id' media.json)" --arg time "$SCHEDULED_AT" '{
title: "A small product update", targetPlatforms: ["x_twitter", "youtube"],
contentFormat: "video", proposedAt: $time, requestReview: false,
threadParts: [{body: "A small product update.", sortOrder: 0, mediaIds: [$media]}],
platformText: [
{platform: "x_twitter", caption: "Here is the update in one minute."},
{platform: "youtube", title: "A small product update", caption: "A quick tour of what changed."}
]
}' > post-body.json
curl --fail-with-body --silent --show-error -X POST https://surgr.ai/api/v1/posts \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: example-video-create-1" \
--data @post-body.json -o post.json5Schedule with the granted permission
This trusted-connection call approves its own draft and queues eligible targets. A quota-locked response stays draft; an HTTP 200 is not a published receipt. Read the post again to inspect delivery.
POST_ID=$(jq -r '.id' post.json)
jq -n --arg time "$SCHEDULED_AT" --argjson revision "$(jq '.revision' post.json)" '{
scheduledAt: $time, expectedRevision: $revision, requestId: "example-video-schedule-1"
}' > schedule-body.json
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts/$POST_ID/schedule" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data @schedule-body.jsonResponses and errors
Examples use invented IDs, reserved example.invalid URLs and illustrative values. Replace them before use. An empty list returns []; there is no example data from a real account.
scheduled means queued, not published. A quota-locked schedule may return HTTP 200 with status: draft and autopublishLocked: true. Check the post’s targets for delivery, errors and platform URLs.
Shared failures return 401 for authentication or 429 for configured rate limiting. JSON errors may include error, code, details or currentRevision. Rate limits use IP-based categories and can include Retry-After and X-RateLimit-* headers; development bypasses them.
For an agent-readable copy, use the full Markdown reference or OpenAPI JSON.
Account
GET/api/v1/account
Read account, workspace, connected accounts and this key’s effective scope.
Fields, examples & errors
- Connections are restricted to allowed platforms. X uses the response key x, while platform enums use x_twitter. Subscription plan is workspace-derived.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/account" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"id": "user_example",
"name": "Example founder",
"email": "[email protected]",
"image": null,
"timezone": "Asia/Taipei",
"createdAt": "2030-01-01T00:00:00.000Z",
"subscriptionStatus": null,
"plan": "free",
"workspace": {
"id": "workspace_example"
},
"connections": {
"x": {
"username": null,
"connectedAt": null
}
},
"agentConnection": {
"name": "Example agent",
"accessMode": "prepare_review",
"permissions": [
"draft"
],
"allowedPlatforms": [
"x_twitter"
]
}
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 404
- Account or workspace not found.
Posts
GET/api/v1/backlog
Read all scoped drafts, newest first; no pagination or query filters.
Fields, examples & errors
- Returns an array of post records, with threadParts, selected target delivery fields, and _count.threadParts/_count.targets. An empty backlog returns [].
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/backlog" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
[]Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
GET/api/v1/posts
List scoped posts with filters, ordering and pagination.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
status | "draft" | "scheduled" | "publishing" | "published" | "failed" | No | Filter by draft, scheduled, publishing, published or failed. |
platform | "x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest" | No | Exact platform enum; filters the primary post.platform field, not all targets. |
search | string | No | At most 200 characters; case-insensitive title or thread-part search. |
tags | string | No | Comma-separated tags; matches any tag after trimming empty entries. |
page | integer | No | Integer ≥1; default 1. |
limit | integer | No | Integer 1–100; default 20. |
sortBy | "createdAt" | "updatedAt" | "scheduledAt" | "sortOrder" | No | createdAt, updatedAt, scheduledAt or sortOrder; default createdAt. |
sortOrder | "asc" | "desc" | No | asc or desc; default desc. |
scheduledAtGte | string | No | Accepted and validated ISO datetime, but the current list handler does not apply this filter. Use timeline from/to for a date range. |
scheduledAtLte | string | No | Accepted and validated ISO datetime, but the current list handler does not apply this filter. Use timeline from/to for a date range. |
statusOptional"draft" | "scheduled" | "publishing" | "published" | "failed"Filter by draft, scheduled, publishing, published or failed.
platformOptional"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest"Exact platform enum; filters the primary post.platform field, not all targets.
searchOptionalstringAt most 200 characters; case-insensitive title or thread-part search.
tagsOptionalstringComma-separated tags; matches any tag after trimming empty entries.
pageOptionalintegerInteger ≥1; default 1.
limitOptionalintegerInteger 1–100; default 20.
sortByOptional"createdAt" | "updatedAt" | "scheduledAt" | "sortOrder"createdAt, updatedAt, scheduledAt or sortOrder; default createdAt.
sortOrderOptional"asc" | "desc"asc or desc; default desc.
scheduledAtGteOptionalstringAccepted and validated ISO datetime, but the current list handler does not apply this filter. Use timeline from/to for a date range.
scheduledAtLteOptionalstringAccepted and validated ISO datetime, but the current list handler does not apply this filter. Use timeline from/to for a date range.
- scheduledAtGte and scheduledAtLte are validated but currently ignored by this handler. Use /timeline from/to instead.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/posts" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"data": [
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "needs_review",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 0,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null,
"media": []
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "draft",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
],
"_count": {
"threadParts": 1
}
}
],
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Query validation failed.
POST/api/v1/posts
Create a draft with thread parts, media and per-platform text.
Fields, examples & errors
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
title | string | No | Shared title: at most 280 characters. Update accepts null to clear it. |
platform | "x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest" | No | Primary platform enum; create defaults to x_twitter. The first normalized target becomes primary. |
targetPlatforms | Array<"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest"> | No | Up to 6 platform enum values. Empty or omitted falls back to platform; duplicates are normalized. |
contentFormat | "text" | "thread" | "single_image" | "carousel" | "video" | "document" | "mixed" | No | Exact accepted format enum; create defaults to text. Media can cause the handler to infer a different format. |
threadParts | object[] | Yes | At least 1 object. Each has body (1–4000 chars), sortOrder (integer ≥0) and optional mediaIds (up to 10 owned media IDs). On PATCH, missing or empty mediaIds keeps existing media for that sortOrder; it does not detach it. |
tags | string[] | No | Up to 10 strings, each at most 50 characters. |
source | "web" | "api" | "claude" | "openclaw" | "generated" | No | Accepted source enum, default web in validation; this API handler always stores api. |
notes | string | No | At most 5000 characters. Update accepts null to clear it. |
proposedAt | string | No | ISO 8601 datetime with timezone offset. Proposes a time; does not schedule. PATCH also accepts null. |
requestReview | boolean | No | True sends the draft to the Inbox. Create defaults false; does not approve it. |
linkUrl | string | null | No | Absolute URL, trimmed, at most 2048 characters; null clears it. {link} in text is resolved by applicable workspace link rules. |
campaign | string | null | No | Trimmed and lowercased; pattern ^[a-z0-9][a-z0-9_-]{0,79}$. Null clears it. |
campaignContent | string | null | No | Same campaign-tag pattern; identifies one piece of content. Null clears it. |
platformText | object[] | No | Up to 6 unique platform entries: platform (exact enum), optional caption (≤4000 chars, nullable), optional title (≤280 chars, nullable). Null or empty caption/title clears that field’s override and uses shared text. Top-level platformText must be an array, not null. On PATCH, omitted or [] preserves existing overrides. Only selected targets receive the overrides. |
titleOptionalstringShared title: at most 280 characters. Update accepts null to clear it.
platformOptional"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest"Primary platform enum; create defaults to x_twitter. The first normalized target becomes primary.
targetPlatformsOptionalArray<"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest">Up to 6 platform enum values. Empty or omitted falls back to platform; duplicates are normalized.
contentFormatOptional"text" | "thread" | "single_image" | "carousel" | "video" | "document" | "mixed"Exact accepted format enum; create defaults to text. Media can cause the handler to infer a different format.
threadPartsRequiredobject[]At least 1 object. Each has body (1–4000 chars), sortOrder (integer ≥0) and optional mediaIds (up to 10 owned media IDs). On PATCH, missing or empty mediaIds keeps existing media for that sortOrder; it does not detach it.
tagsOptionalstring[]Up to 10 strings, each at most 50 characters.
sourceOptional"web" | "api" | "claude" | "openclaw" | "generated"Accepted source enum, default web in validation; this API handler always stores api.
notesOptionalstringAt most 5000 characters. Update accepts null to clear it.
proposedAtOptionalstringISO 8601 datetime with timezone offset. Proposes a time; does not schedule. PATCH also accepts null.
requestReviewOptionalbooleanTrue sends the draft to the Inbox. Create defaults false; does not approve it.
linkUrlOptionalstring | nullAbsolute URL, trimmed, at most 2048 characters; null clears it. {link} in text is resolved by applicable workspace link rules.
campaignOptionalstring | nullTrimmed and lowercased; pattern ^[a-z0-9][a-z0-9_-]{0,79}$. Null clears it.
campaignContentOptionalstring | nullSame campaign-tag pattern; identifies one piece of content. Null clears it.
platformTextOptionalobject[]Up to 6 unique platform entries: platform (exact enum), optional caption (≤4000 chars, nullable), optional title (≤280 chars, nullable). Null or empty caption/title clears that field’s override and uses shared text. Top-level platformText must be an array, not null. On PATCH, omitted or [] preserves existing overrides. Only selected targets receive the overrides.
- Idempotency-Key replays the earlier draft with HTTP 200 and Idempotency-Replayed: true. This handler does not compare the new payload with the original; never reuse a key for new content.
- The response is the post record, including targets and threadParts with media joins. source is always api.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Idempotency-Key: example-create-1" \
-H "Content-Type: application/json" \
--data '{"title":"Launch update","targetPlatforms":["x_twitter"],"contentFormat":"text","threadParts":[{"body":"A small launch update.","sortOrder":0}],"tags":["launch"],"requestReview":true,"platformText":[{"platform":"x_twitter","caption":"A small launch update."}]}'Success example · HTTP 201
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "needs_review",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 0,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null,
"media": []
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "draft",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
]
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Body validation failed.
- 403
- Missing draft permission, disallowed platform or media outside this workspace.
POST/api/v1/posts/bulk
Create 1–50 drafts in one transaction.
Fields, examples & errors
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
posts | object[] | Yes | 1–50 create-post objects; nested fields and validation match POST /api/v1/posts. The batch is transactional. |
postsRequiredobject[]1–50 create-post objects; nested fields and validation match POST /api/v1/posts. The batch is transactional.
- Response posts include threadParts/media joins; this handler does not include a targets relation in its hydrated response.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts/bulk" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"posts":[{"title":"Launch update","targetPlatforms":["x_twitter"],"contentFormat":"text","threadParts":[{"body":"A small launch update.","sortOrder":0}],"tags":["launch"],"requestReview":true,"platformText":[{"platform":"x_twitter","caption":"A small launch update."}]}]}'Success example · HTTP 201
{
"created": 1,
"posts": [
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "needs_review",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 0,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null,
"media": []
}
]
}
]
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Invalid batch or create-post body.
- 403
- Missing draft permission, disallowed platform or foreign media.
GET/api/v1/posts/{id}
Read a scoped post, its targets, feedback and recent activity.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
- Includes threadParts/media, targets, the latest 5 publishLogs, all changeRequests and latest 20 integrationActivities.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/posts/post_example" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "needs_review",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 0,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null,
"media": []
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "draft",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
],
"publishLogs": [],
"changeRequests": [],
"integrationActivities": []
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Post ID missing.
- 403
- Post outside the workspace or allowed platform scope.
- 404
- Post not found.
PATCH/api/v1/posts/{id}
Edit a scoped mutable post; material edits clear its approval and schedule.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
title | string | null | No | Shared title: at most 280 characters. Update accepts null to clear it. |
platform | "x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest" | No | Primary platform enum; create defaults to x_twitter. The first normalized target becomes primary. |
targetPlatforms | Array<"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest"> | No | Up to 6 platform enum values. Empty or omitted falls back to platform; duplicates are normalized. |
contentFormat | "text" | "thread" | "single_image" | "carousel" | "video" | "document" | "mixed" | No | Exact accepted format enum; create defaults to text. Media can cause the handler to infer a different format. |
tags | string[] | No | Up to 10 strings, each at most 50 characters. |
threadParts | object[] | No | At least 1 object. Each has body (1–4000 chars), sortOrder (integer ≥0) and optional mediaIds (up to 10 owned media IDs). On PATCH, missing or empty mediaIds keeps existing media for that sortOrder; it does not detach it. |
notes | string | null | No | At most 5000 characters. Update accepts null to clear it. |
scheduledAt | string | null | No | ISO 8601 datetime with timezone offset. Schedule endpoint requires a future time; PATCH accepts it only for legacy_full keys (or returns 403). |
status | "draft" | "scheduled" | "publishing" | "failed" | No | PATCH accepts draft, scheduled, publishing or failed only for legacy_full keys. Modern scoped keys must use schedule/unschedule operations. |
proposedAt | string | null | No | ISO 8601 datetime with timezone offset. Proposes a time; does not schedule. PATCH also accepts null. |
requestReview | boolean | No | True sends the draft to the Inbox. Create defaults false; does not approve it. |
expectedRevision | integer | No | Optional integer ≥1; mismatches return 409 stale_revision. Use the revision returned by the last read. |
linkUrl | string | null | No | Absolute URL, trimmed, at most 2048 characters; null clears it. {link} in text is resolved by applicable workspace link rules. |
campaign | string | null | No | Trimmed and lowercased; pattern ^[a-z0-9][a-z0-9_-]{0,79}$. Null clears it. |
campaignContent | string | null | No | Same campaign-tag pattern; identifies one piece of content. Null clears it. |
platformText | object[] | No | Up to 6 unique platform entries: platform (exact enum), optional caption (≤4000 chars, nullable), optional title (≤280 chars, nullable). Null or empty caption/title clears that field’s override and uses shared text. Top-level platformText must be an array, not null. On PATCH, omitted or [] preserves existing overrides. Only selected targets receive the overrides. |
titleOptionalstring | nullShared title: at most 280 characters. Update accepts null to clear it.
platformOptional"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest"Primary platform enum; create defaults to x_twitter. The first normalized target becomes primary.
targetPlatformsOptionalArray<"x_twitter" | "instagram" | "tiktok" | "youtube" | "linkedin" | "pinterest">Up to 6 platform enum values. Empty or omitted falls back to platform; duplicates are normalized.
contentFormatOptional"text" | "thread" | "single_image" | "carousel" | "video" | "document" | "mixed"Exact accepted format enum; create defaults to text. Media can cause the handler to infer a different format.
tagsOptionalstring[]Up to 10 strings, each at most 50 characters.
threadPartsOptionalobject[]At least 1 object. Each has body (1–4000 chars), sortOrder (integer ≥0) and optional mediaIds (up to 10 owned media IDs). On PATCH, missing or empty mediaIds keeps existing media for that sortOrder; it does not detach it.
notesOptionalstring | nullAt most 5000 characters. Update accepts null to clear it.
scheduledAtOptionalstring | nullISO 8601 datetime with timezone offset. Schedule endpoint requires a future time; PATCH accepts it only for legacy_full keys (or returns 403).
statusOptional"draft" | "scheduled" | "publishing" | "failed"PATCH accepts draft, scheduled, publishing or failed only for legacy_full keys. Modern scoped keys must use schedule/unschedule operations.
proposedAtOptionalstring | nullISO 8601 datetime with timezone offset. Proposes a time; does not schedule. PATCH also accepts null.
requestReviewOptionalbooleanTrue sends the draft to the Inbox. Create defaults false; does not approve it.
expectedRevisionOptionalintegerOptional integer ≥1; mismatches return 409 stale_revision. Use the revision returned by the last read.
linkUrlOptionalstring | nullAbsolute URL, trimmed, at most 2048 characters; null clears it. {link} in text is resolved by applicable workspace link rules.
campaignOptionalstring | nullTrimmed and lowercased; pattern ^[a-z0-9][a-z0-9_-]{0,79}$. Null clears it.
campaignContentOptionalstring | nullSame campaign-tag pattern; identifies one piece of content. Null clears it.
platformTextOptionalobject[]Up to 6 unique platform entries: platform (exact enum), optional caption (≤4000 chars, nullable), optional title (≤280 chars, nullable). Null or empty caption/title clears that field’s override and uses shared text. Top-level platformText must be an array, not null. On PATCH, omitted or [] preserves existing overrides. Only selected targets receive the overrides.
- scheduledAt/status are schema-accepted but forbidden for non-legacy keys; use scheduling operations.
- An edit of any already-approved post clears approval/schedule. threadParts, platforms, format and link changes also invalidate the revision. PATCH with missing or empty mediaIds preserves the prior media at that sortOrder.
Request
curl --fail-with-body --silent --show-error -X PATCH "https://surgr.ai/api/v1/posts/post_example" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"title":"Updated launch note","expectedRevision":1}'Success example · HTTP 200
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Updated launch note",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "needs_review",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 0,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null,
"media": []
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "draft",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
]
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Missing ID or body validation failed.
- 403
- Permission/scope refusal, foreign media, or delivery-state fields on a non-legacy key.
- 404
- Post not found.
- 409
- Stale revision, publishing in progress, or published post cannot be changed.
DELETE/api/v1/posts/{id}
Delete a mutable post and clean up media with no remaining references.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
- Irreversible deletion; shared media still attached elsewhere is preserved. Storage cleanup is asynchronous best effort.
Request
curl --fail-with-body --silent --show-error -X DELETE "https://surgr.ai/api/v1/posts/post_example" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"success": true
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Post ID missing.
- 403
- Permission or workspace/platform scope refusal.
- 404
- Post not found.
- 409
- Publishing in progress or already-published post cannot be deleted.
Scheduling
POST/api/v1/posts/{id}/schedule
Queue eligible targets at a future time, preserving or recording a matching approval.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
scheduledAt | string | Yes | ISO 8601 datetime with timezone offset. Schedule endpoint requires a future time; PATCH accepts it only for legacy_full keys (or returns 403). |
expectedRevision | integer | No | Optional integer ≥1; mismatches return 409 stale_revision. Use the revision returned by the last read. |
requestId | string | No | Optional string, 1–200 characters. Schedule idempotency ID; falls back to the Idempotency-Key header. Do not reuse for another payload. |
scheduledAtRequiredstringISO 8601 datetime with timezone offset. Schedule endpoint requires a future time; PATCH accepts it only for legacy_full keys (or returns 403).
expectedRevisionOptionalintegerOptional integer ≥1; mismatches return 409 stale_revision. Use the revision returned by the last read.
requestIdOptionalstringOptional string, 1–200 characters. Schedule idempotency ID; falls back to the Idempotency-Key header. Do not reuse for another payload.
- For review-first use, create/request review, then stop for a person to use Inbox Approve & schedule; that action already queues the post. dispatch_approved is only for re-dispatching a still-current exact approval: first reread the post and preserve its approved revision, destinations and time. schedule can self-approve an unapproved draft; do not grant it if every post must get human review.
- A quota-limited Free account can receive HTTP 200 with status draft, autopublishEnabled false and autopublishLocked true: it was not queued. Normal responses contain targets/threadParts; quota responses include threadParts but not targets.
- requestId takes precedence over Idempotency-Key. Replays return HTTP 200 / Idempotency-Replayed: true; changed payloads return 409. A request still in progress can return Retry-After: 30.
- An HTTP 200 scheduled response confirms queueing, not successful publication. Check GET /posts/{id} for each target’s status/lastError/platformUrl.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts/post_example/schedule" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"scheduledAt":"2030-01-02T09:00:00Z","expectedRevision":1,"requestId":"example-schedule-1"}'Success example · HTTP 200
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "scheduled",
"revision": 1,
"reviewState": "approved",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": 1,
"approvedAt": "2030-01-01T00:00:00.000Z",
"approvedByUserId": "user_example",
"approvedByApiKeyId": null,
"approvedPlatforms": [
"x_twitter"
],
"approvedScheduledAt": "2030-01-02T09:00:00.000Z",
"scheduleRevision": 1,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": "2030-01-02T09:00:00.000Z",
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": "message_example",
"autopublishEnabled": true,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "scheduled",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": "2030-01-02T09:00:00.000Z",
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": "message_example",
"autopublishEnabled": true,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
]
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Missing ID, invalid body, past time, or no thread content.
- 403
- Ownership, platform or permission refusal.
- 404
- Post not found.
- 409
- Publishing paused, no eligible targets, stale/changed approval, changed revision, idempotency conflict or request in progress.
- 502
- Failed to queue publish jobs.
POST/api/v1/posts/{id}/unschedule
Cancel a scheduled post, return it to draft and clear scoped approval.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
- No request body. The response includes targets and threadParts; scheduleRevision increases.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts/post_example/unschedule" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"id": "post_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"title": "Launch update",
"platform": "x_twitter",
"targetPlatforms": [
"x_twitter"
],
"contentFormat": "text",
"status": "draft",
"revision": 1,
"reviewState": "draft",
"reviewEnforcedAt": "2030-01-01T00:00:00.000Z",
"reviewRequestedAt": "2030-01-01T00:00:00.000Z",
"proposedAt": null,
"approvedRevision": null,
"approvedAt": null,
"approvedByUserId": null,
"approvedByApiKeyId": null,
"approvedPlatforms": [],
"approvedScheduledAt": null,
"scheduleRevision": 2,
"createdByApiKeyId": "connection_example",
"createdByName": "Example agent",
"clientRequestId": null,
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null,
"tags": [
"launch"
],
"source": "api",
"linkUrl": null,
"campaign": null,
"campaignContent": null,
"notes": null,
"sortOrder": 0,
"threadParts": [
{
"id": "part_example",
"postId": "post_example",
"body": "A small launch update.",
"sortOrder": 0,
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"platformPartId": null
}
],
"targets": [
{
"id": "target_example",
"postId": "post_example",
"platform": "x_twitter",
"mode": "x_text",
"requiresManual": false,
"unsupportedReason": null,
"metadata": null,
"caption": "A small launch update.",
"title": null,
"status": "draft",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"scheduledAt": null,
"publishedAt": null,
"platformPostId": null,
"platformUrl": null,
"qstashMessageId": null,
"autopublishEnabled": false,
"lastError": null,
"retryCount": 0,
"maxRetries": 3,
"publishingLockId": null,
"publishingLockedAt": null
}
]
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Missing ID or post is not scheduled.
- 403
- Ownership, platform or cancellation permission refusal.
- 404
- Post not found.
- 409
- Post began publishing during cancellation.
POST/api/v1/posts/{id}/publish-now
Enqueue eligible targets immediately; this is not a published receipt.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
- Prefer schedule plus human review. No request body. For scoped keys this records the connection’s approval; it does not require prior human review.
- Response status scheduled means queued. Re-read the post/targets for published or failed delivery.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/posts/post_example/publish-now" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"id": "post_example",
"status": "scheduled",
"targetCount": 1,
"message": "Publishing in progress \u2014 this may take a moment for posts with video."
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Missing ID or no thread content.
- 403
- Ownership, platform or permission refusal.
- 404
- Post not found.
- 409
- No eligible targets, publishing in progress or already-published post.
Media
POST/api/v1/media/presign
Reserve media and obtain a 5-minute direct-upload URL.
Fields, examples & errors
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
fileName | string | Yes | Filename, 1–255 characters. Case differs from URL-upload filename. |
fileType | string | Yes | MIME type, 1–127 characters; allowed: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime. |
fileSize | integer | Yes | Positive integer byte count, up to 104857600 (100 MiB). Must match the uploaded object. |
fileNameRequiredstringFilename, 1–255 characters. Case differs from URL-upload filename.
fileTypeRequiredstringMIME type, 1–127 characters; allowed: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime.
fileSizeRequiredintegerPositive integer byte count, up to 104857600 (100 MiB). Must match the uploaded object.
- PUT the exact byte count and Content-Type to uploadUrl, then confirm media.id. Do not send the Surgr bearer key to the presigned upload host.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/media/presign" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"fileName":"demo.mp4","fileType":"video/mp4","fileSize":1000000}'Success example · HTTP 201
{
"uploadUrl": "https://upload.example.invalid/demo.mp4?example=presigned",
"media": {
"id": "media_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"filename": "demo.mp4",
"mimeType": "video/mp4",
"sizeBytes": 1000000,
"width": null,
"height": null,
"durationMs": null,
"storageUrl": "",
"storageKey": "media/user_example/demo.mp4",
"processingStatus": "processing"
}
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Invalid fields, unsupported MIME or file larger than 104857600 bytes.
- 403
- Missing draft permission.
POST/api/v1/media/upload
Fetch a public URL server-side and create ready media.
Fields, examples & errors
JSON body
| Field | Type | Required | Details |
|---|---|---|---|
url | string | Yes | Public absolute URL; the server downloads it. Private/local destinations and unsafe redirects are refused; download limit 10485760 bytes (10 MiB). |
filename | string | No | Optional filename override, at most 255 characters; otherwise derived from URL path. |
mimeType | string | No | Optional MIME override, at most 127 characters; otherwise downloaded content type (or application/octet-stream). |
urlRequiredstringPublic absolute URL; the server downloads it. Private/local destinations and unsafe redirects are refused; download limit 10485760 bytes (10 MiB).
filenameOptionalstringOptional filename override, at most 255 characters; otherwise derived from URL path.
mimeTypeOptionalstringOptional MIME override, at most 127 characters; otherwise downloaded content type (or application/octet-stream).
- Uses different field casing from presign: filename/mimeType. No confirm call needed; processingStatus is ready. Public media URLs must be replaceable fake examples here.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/media/upload" \
-H "Authorization: Bearer $SURGR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"url":"https://assets.example.invalid/demo.mp4","filename":"demo.mp4","mimeType":"video/mp4"}'Success example · HTTP 201
{
"id": "media_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"filename": "demo.mp4",
"mimeType": "video/mp4",
"sizeBytes": 1000000,
"width": null,
"height": null,
"durationMs": null,
"storageUrl": "https://assets.example.invalid/demo.mp4",
"storageKey": "media/user_example/demo.mp4",
"processingStatus": "ready"
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Invalid body, unsafe/private URL or failed/oversized download.
- 403
- Missing draft permission.
POST/api/v1/media/{id}/confirm
Verify a presigned object and mark reserved media ready.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
id | string | Yes | ID returned by the relevant create/presign response; fake example IDs must be replaced. |
idRequiredstringID returned by the relevant create/presign response; fake example IDs must be replaced.
- No request body. Confirm the ID returned by presign after the PUT completes; not idempotent once processingStatus is ready.
Request
curl --fail-with-body --silent --show-error -X POST "https://surgr.ai/api/v1/media/media_example/confirm" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"id": "media_example",
"userId": "user_example",
"workspaceId": "workspace_example",
"createdAt": "2030-01-01T00:00:00.000Z",
"updatedAt": "2030-01-01T00:00:00.000Z",
"filename": "demo.mp4",
"mimeType": "video/mp4",
"sizeBytes": 1000000,
"width": null,
"height": null,
"durationMs": null,
"storageUrl": "https://assets.example.invalid/demo.mp4",
"storageKey": "media/user_example/demo.mp4",
"processingStatus": "ready"
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Missing ID or stored upload fails size/content-type verification.
- 403
- Missing draft permission or foreign workspace media.
- 404
- Media not found.
- 409
- Media is not in processing state.
Results
GET/api/v1/stats
Read scoped queue counts, next schedule, seven-day gaps and up to five failed/stuck posts.
Fields, examples & errors
- No query fields. Failed posts include id, preview, lastError, failedAt and targetErrors (platform, status, message, failedAt). Stale publishing/scheduled targets use a 10-minute threshold.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/stats" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"totalPosts": 0,
"published": 0,
"failed": 0,
"scheduled": 0,
"drafts": 0,
"successRate": 100,
"nextScheduledAt": null,
"nextScheduledPreview": null,
"gapDays": 7,
"scheduledByDay": [
{
"date": "2030-01-01",
"count": 0
},
{
"date": "2030-01-02",
"count": 0
},
{
"date": "2030-01-03",
"count": 0
},
{
"date": "2030-01-04",
"count": 0
},
{
"date": "2030-01-05",
"count": 0
},
{
"date": "2030-01-06",
"count": 0
},
{
"date": "2030-01-07",
"count": 0
}
],
"failedPosts": []
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
GET/api/v1/results
Read scoped 30-day growth, post metrics, lessons and metric sync status.
Fields, examples & errors
- No query fields. summary, growth, daily, followers, topPosts, lessons and syncStatus are returned. Null/stale metrics must not be turned into invented results.
- daily contains 30 daily points. topPosts/bestPost include per-destination counts; syncStatus reports lastAttemptAt, lastSuccessAt and lastError for connected platforms.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/results" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
{
"summary": {
"followersCurrent": null,
"followersDelta7d": null,
"followersDelta30d": null,
"impressions7d": 0,
"impressions30d": 0,
"postsPublished7d": 0,
"postsPublished30d": 0,
"engagement30d": 0,
"averageImpressionsPerPost30d": 0,
"bestPost30d": null,
"lastMetricsSyncAt": null,
"xConnected": false
},
"growth": {
"audienceCurrent": null,
"audienceDelta7d": null,
"audienceDeltaPrevious7d": null,
"followersCurrent": null,
"followersDelta7d": null,
"followersDeltaPrevious7d": null,
"exposure7d": 0,
"exposurePrevious7d": 0,
"impressions7d": 0,
"impressionsPrevious7d": 0,
"postsPublished7d": 0,
"postsPublishedPrevious7d": 0,
"engagement7d": 0,
"engagementPrevious7d": 0,
"lastMetricsSyncAt": null,
"bestPost7d": null,
"platformBreakdown": [],
"nextAction": {
"label": "Connect a platform to collect results.",
"href": "/settings",
"tone": "warning"
},
"xConnected": false,
"connectedPlatforms": []
},
"daily": [
{
"date": "2030-01-02",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-03",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-04",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-05",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-06",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-07",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-08",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-09",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-10",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-11",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-12",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-13",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-14",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-15",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-16",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-17",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-18",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-19",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-20",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-21",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-22",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-23",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-24",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-25",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-26",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-27",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-28",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-29",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-30",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
},
{
"date": "2030-01-31",
"postsPublished": 0,
"impressionCount": 0,
"engagementCount": 0
}
],
"followers": [],
"topPosts": [],
"lessons": [
{
"title": "Connect a platform",
"detail": "Connect a social platform to start collecting result snapshots.",
"tone": "warning"
}
],
"syncStatus": []
}Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
GET/api/v1/tags
Read distinct tags and scoped post counts, most frequent first.
Fields, examples & errors
- No query fields. Counts each tag once per post; ties sort by tag.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/tags" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
[
{
"tag": "launch",
"count": 1
}
]Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
GET/api/v1/timeline
Read scoped scheduled, publishing and published posts ordered by scheduledAt.
Fields, examples & errors
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
from | string | No | Optional inclusive scheduledAt lower bound; ISO datetime with offset. |
to | string | No | Optional inclusive scheduledAt upper bound; ISO datetime with offset. |
fromOptionalstringOptional inclusive scheduledAt lower bound; ISO datetime with offset.
toOptionalstringOptional inclusive scheduledAt upper bound; ISO datetime with offset.
- Only posts with a scheduledAt are returned. Bounds are inclusive; no default range or pagination. Raw post records include threadParts, selected target delivery fields and _count.threadParts/_count.targets.
Request
curl --fail-with-body --silent --show-error -X GET "https://surgr.ai/api/v1/timeline" \
-H "Authorization: Bearer $SURGR_API_KEY"Success example · HTTP 200
[]Errors
- 401
- Missing, invalid, expired or revoked bearer key.
- 429
- Configured rate limit exceeded or client IP unavailable.
- 500
- Handler or middleware failed.
- 400
- Invalid from/to datetime.
Prefer tools to HTTP?
Connect through MCP with the same key and permissions. All documentation.