# Surgr API reference Base URL: https://surgr.ai/api/v1 Auth: Authorization: Bearer . Keep the key in your own SURGR_API_KEY environment variable. Never send it to a media upload host. Keys are scoped by the permissions you tick for that connection. A missing permission returns 403. All reads still require a valid key and apply workspace/platform scope; missing or changed approvals can return 409. ## Create a connection 1. Sign in to Surgr, open Agents and choose Connect agent. 2. Fill Connection name. Under Access, choose Prepare for review (recommended), or tick the exact permissions you intend to grant. 3. Tick the connected platforms under Allowed accounts. Choose Create connection. 4. 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. 5. Optionally choose Test read-only connection, then Done. The same key works for REST and MCP. ## 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. ### 1. Presign the video Choose your local video and exact byte count. This response reserves media in processing state; the upload URL expires in 300 seconds. ```sh 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.json ``` ### 2. Upload 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. ```sh 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.mp4 ``` ### 3. Confirm the media Confirm media.id after the PUT succeeds. The handler verifies stored size and MIME type before marking it ready. ```sh 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.json ``` ### 4. Create 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. ```sh 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.json ``` ### 5. Schedule 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. ```sh 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.json ``` ## Response and error conventions Examples use invented IDs, reserved example.invalid URLs and illustrative values. Success examples show the handler’s response shape; empty arrays represent no records. Every mutation is asynchronous where stated: scheduled is not published. Do not retry a changed payload under the same idempotency key. Shared auth failures return 401; configured rate limiting can return 429 with Retry-After and X-RateLimit-* headers. Errors are JSON, typically error plus optional code, details or currentRevision. Rate limiting is bypassed in development and requires configured infrastructure in production. ## Account ### GET /api/v1/account Read account, workspace, connected accounts and this key’s effective scope. Permission: No action permission; valid scoped key required for reads. - Connections are restricted to allowed platforms. X uses the response key x, while platform enums use x_twitter. Subscription plan is workspace-derived. #### Request ```sh 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) ```json { "id": "user_example", "name": "Example founder", "email": "founder@example.invalid", "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 | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. - Returns an array of post records, with threadParts, selected target delivery fields, and _count.threadParts/_count.targets. An empty backlog returns []. #### Request ```sh 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) ```json [] ``` #### Errors | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. #### 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. | - scheduledAtGte and scheduledAtLte are validated but currently ignored by this handler. Use /timeline from/to instead. #### Request ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft #### 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. | - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft #### 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. | - Response posts include threadParts/media joins; this handler does not include a targets relation in its hydrated response. #### Request ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft; also manage_own for self-approved posts or manage_approved for posts approved by a person/another connection #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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. | - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft; also manage_own for self-approved posts or manage_approved for posts approved by a person/another connection #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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 ```sh 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) ```json { "success": true } ``` #### Errors | Status | When | | --- | --- | | 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. Permission: schedule for new drafts; manage_own for its approvals; dispatch_approved or manage_approved for matching approvals by others #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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. | - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: manage_own for self-approved posts; manage_approved for other or missing approvals #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: publish_now; also manage_own or manage_approved for an already-approved post #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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 ```sh 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) ```json { "id": "post_example", "status": "scheduled", "targetCount": 1, "message": "Publishing in progress \u2014 this may take a moment for posts with video." } ``` #### Errors | Status | When | | --- | --- | | 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. Permission: draft #### 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. | - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft #### 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). | - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: draft #### Parameters | Field | Type | Required | Details | | --- | --- | --- | --- | | id | string | Yes | ID 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. - 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 ```sh 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) ```json { "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 | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. - No query fields. Counts each tag once per post; ties sort by tag. #### Request ```sh 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) ```json [ { "tag": "launch", "count": 1 } ] ``` #### Errors | Status | When | | --- | --- | | 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. Permission: No action permission; valid scoped key required for reads. #### 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. | - 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 ```sh 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) ```json [] ``` #### Errors | Status | When | | --- | --- | | 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. | ## Related documentation - [REST reference](https://surgr.ai/docs/api) - [OpenAPI JSON](https://surgr.ai/openapi.json) - [MCP connection guide](https://surgr.ai/mcp) - [Agent connections](https://surgr.ai/agents)