{"openapi":"3.1.0","info":{"title":"PostMantis API","version":"1.0.0","description":"Public API for social media automation. List connected profiles, create and manage posts, upload media, and inspect publishing outcomes. All endpoints require Bearer token authentication using a PostMantis API key."},"servers":[{"url":"https://postmantis.com","description":"Configured server"}],"tags":[{"name":"Profiles","description":"Connected social accounts available for publishing. Agents list profiles first, then use profile ids when creating posts."},{"name":"Posts","description":"Create, read, update, cancel, and list posts. Supports publish-now and scheduled publishing across multiple providers."},{"name":"Uploads","description":"Two-phase direct-to-storage upload flow. Callers request a presigned URL, PUT bytes, then finalize to receive a verified artifact id for use in posts."},{"name":"Publishing Logs","description":"Read-only delivery event stream. Shows what happened to each post delivery (pending, published, failed, canceled) with timestamps and provider links."}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"PostMantis API key from the dashboard. Include as `Authorization: Bearer <key>`."}},"schemas":{"ProfilesListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Profile UUID. Use as the target id in POST /api/v1/posts profiles array."},"name":{"type":"string","description":"Human-readable profile label set in the dashboard."},"provider":{"type":"string","enum":["x","tiktok","pinterest"],"description":"Social provider for this connection, e.g. x, linkedin, reddit, pinterest."},"status":{"type":"string","enum":["active","reauthRequired","disconnected"],"description":"Connection state. active = usable; reauthRequired = token expired, reconnect in dashboard; disconnected = no longer linked."},"username":{"type":"string","description":"Handle or username at the provider, when available."},"pictureUrl":{"type":"string","format":"uri","description":"Avatar URL from the provider, when available."},"expiresAt":{"type":"string","format":"date-time","description":"Token expiry time. Approaching expiry means reauthRequired is imminent."},"postsCount":{"type":"integer","minimum":0,"description":"Number of posts ever published through this profile."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the profile was connected."},"updatedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the profile was last refreshed."}},"required":["id","name","provider","status","postsCount","createdAt","updatedAt"],"additionalProperties":false},"description":"All profiles visible to the caller's API-key scope."}},"required":["data"],"additionalProperties":false},"ProblemDetails":{"type":"object","properties":{"type":{"type":"string","format":"uri","description":"URI identifying the problem type."},"title":{"type":"string","description":"Short human-readable summary of the problem."},"status":{"type":"integer","exclusiveMinimum":0,"description":"HTTP status code reproduced in the body."},"detail":{"type":"string","description":"Human-readable explanation specific to this occurrence."},"code":{"type":"string","description":"Stable machine code for this error class. Use this for branching, not title/detail."},"errors":{"type":"array","items":{"type":"object","properties":{"path":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Field path that failed validation."},"message":{"type":"string","description":"Validation message for this field."}},"required":["path","message"]},"description":"Field-level validation issues, present on invalid-request (400) responses."}},"required":["type","title","status","detail","code"],"additionalProperties":false},"ProfileEnvelope":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Profile UUID. Use as the target id in POST /api/v1/posts profiles array."},"name":{"type":"string","description":"Human-readable profile label set in the dashboard."},"provider":{"type":"string","enum":["x","tiktok","pinterest"],"description":"Social provider for this connection, e.g. x, linkedin, reddit, pinterest."},"status":{"type":"string","enum":["active","reauthRequired","disconnected"],"description":"Connection state. active = usable; reauthRequired = token expired, reconnect in dashboard; disconnected = no longer linked."},"username":{"type":"string","description":"Handle or username at the provider, when available."},"pictureUrl":{"type":"string","format":"uri","description":"Avatar URL from the provider, when available."},"expiresAt":{"type":"string","format":"date-time","description":"Token expiry time. Approaching expiry means reauthRequired is imminent."},"postsCount":{"type":"integer","minimum":0,"description":"Number of posts ever published through this profile."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the profile was connected."},"updatedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the profile was last refreshed."}},"required":["id","name","provider","status","postsCount","createdAt","updatedAt"],"additionalProperties":false,"description":"The requested profile."}},"required":["data"],"additionalProperties":false},"PaginatedPostsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Post UUID."},"status":{"type":"string","enum":["pending","settled","canceled","scheduled"],"description":"Post lifecycle: pending = accepted, not settled; scheduled = waiting for scheduledAt; settled = all deliveries resolved; canceled = a future delivery was stopped."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the post was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the post last changed."},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Media item id, unique per post."},"artifactId":{"type":"string","format":"uuid","description":"Underlying verified artifact id."},"artifactStatus":{"type":"string","enum":["reserved","verified","rejected"],"description":"Artifact lifecycle: reserved, verified, or rejected."},"contentType":{"type":"string","description":"Verified IANA media type of the artifact."},"url":{"type":"string","format":"uri","description":"Temporary public read URL, when available."},"alt":{"type":"string","description":"Accessibility alt text, when set."}},"required":["id","artifactId"],"additionalProperties":false},"description":"Media attached to the post."},"deliveries":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["x","tiktok","pinterest"],"description":"Social provider for this delivery."},"profileName":{"type":"string","description":"Profile label at delivery time."},"status":{"type":"string","enum":["pending","published","failed","canceled"],"description":"Delivery state: pending, published, failed, or canceled. Poll GET /api/v1/posts/{id} to watch transitions."},"providerOptions":{"type":"object","additionalProperties":{},"description":"Provider-specific options applied to this delivery (e.g. board id, reply settings)."},"profileId":{"type":"string","format":"uuid","description":"Target profile UUID."},"scheduledAt":{"type":"string","format":"date-time","description":"When delivery was scheduled to run."},"publishedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp of successful publish, when published."},"failedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp of failure, when failed."},"providerPostId":{"type":"string","description":"Provider-native id of the published post, when published."},"url":{"type":"string","format":"uri","description":"Public URL of the published post, when available."},"errorMessage":{"type":"string","description":"Failure detail, when status is failed."}},"required":["provider","profileName","status","providerOptions"],"additionalProperties":false},"description":"Per-profile delivery outcomes. Inspect status on each to know what happened where."},"body":{"type":"string","description":"Post body text, when set."},"scheduledAt":{"type":"string","format":"date-time","description":"Scheduled publish time, when set."},"canceledAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp a future delivery was canceled."}},"required":["id","status","createdAt","updatedAt","media","deliveries"],"additionalProperties":false},"description":"Posts on this page. Empty array when the page has no matches."},"pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":0,"description":"Zero-based page index returned."},"perPage":{"type":"integer","exclusiveMinimum":0,"description":"Page size returned."},"total":{"type":"integer","minimum":0,"description":"Total matching posts across all pages."}},"required":["page","perPage","total"],"additionalProperties":false,"description":"Pagination metadata for this result set."}},"required":["data","pagination"],"additionalProperties":false},"PostEnvelope":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Post UUID."},"status":{"type":"string","enum":["pending","settled","canceled","scheduled"],"description":"Post lifecycle: pending = accepted, not settled; scheduled = waiting for scheduledAt; settled = all deliveries resolved; canceled = a future delivery was stopped."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the post was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the post last changed."},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Media item id, unique per post."},"artifactId":{"type":"string","format":"uuid","description":"Underlying verified artifact id."},"artifactStatus":{"type":"string","enum":["reserved","verified","rejected"],"description":"Artifact lifecycle: reserved, verified, or rejected."},"contentType":{"type":"string","description":"Verified IANA media type of the artifact."},"url":{"type":"string","format":"uri","description":"Temporary public read URL, when available."},"alt":{"type":"string","description":"Accessibility alt text, when set."}},"required":["id","artifactId"],"additionalProperties":false},"description":"Media attached to the post."},"deliveries":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["x","tiktok","pinterest"],"description":"Social provider for this delivery."},"profileName":{"type":"string","description":"Profile label at delivery time."},"status":{"type":"string","enum":["pending","published","failed","canceled"],"description":"Delivery state: pending, published, failed, or canceled. Poll GET /api/v1/posts/{id} to watch transitions."},"providerOptions":{"type":"object","additionalProperties":{},"description":"Provider-specific options applied to this delivery (e.g. board id, reply settings)."},"profileId":{"type":"string","format":"uuid","description":"Target profile UUID."},"scheduledAt":{"type":"string","format":"date-time","description":"When delivery was scheduled to run."},"publishedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp of successful publish, when published."},"failedAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp of failure, when failed."},"providerPostId":{"type":"string","description":"Provider-native id of the published post, when published."},"url":{"type":"string","format":"uri","description":"Public URL of the published post, when available."},"errorMessage":{"type":"string","description":"Failure detail, when status is failed."}},"required":["provider","profileName","status","providerOptions"],"additionalProperties":false},"description":"Per-profile delivery outcomes. Inspect status on each to know what happened where."},"body":{"type":"string","description":"Post body text, when set."},"scheduledAt":{"type":"string","format":"date-time","description":"Scheduled publish time, when set."},"canceledAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp a future delivery was canceled."}},"required":["id","status","createdAt","updatedAt","media","deliveries"],"additionalProperties":false,"description":"The requested post."}},"required":["data"],"additionalProperties":false},"CreatePostRequest":{"type":"object","properties":{"post":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Post body text. Trimmed, 1-5000 characters. A post must include body, media, or both."},"scheduledAt":{"type":"string","format":"date-time","description":"Future ISO-8601 datetime to schedule publishing. Omit to publish now. Must be in the future."}},"additionalProperties":false,"description":"Post content and optional schedule."},"profiles":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"description":"Target profile UUIDs from GET /api/v1/profiles. At least one. Duplicates are rejected."},"media":{"type":"array","items":{"type":"object","properties":{"artifactId":{"type":"string","format":"uuid","description":"UUID of a verified artifact returned by POST /api/v1/uploads/complete."},"alt":{"type":"string","minLength":1,"maxLength":1000,"description":"Accessibility alt text for the media item. For images this is strongly recommended."}},"required":["artifactId"],"additionalProperties":false},"minItems":1,"maxItems":20,"description":"Media attachments referencing verified artifact ids. 1-20 items. Required if body is omitted."},"providerOptions":{"type":"object","properties":{"x":{"type":"object","additionalProperties":{}},"tiktok":{"type":"object","additionalProperties":{}},"pinterest":{"type":"object","additionalProperties":{}}},"additionalProperties":false,"description":"Per-provider overrides (e.g. board id for Pinterest). Keys must match selected providers."}},"required":["post","profiles"],"additionalProperties":false},"UpdatePostRequest":{"type":"object","properties":{"post":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Post body text. Trimmed, 1-5000 characters. A post must include body, media, or both."},"scheduledAt":{"type":"string","format":"date-time","description":"New schedule. Set to null to publish immediately. Future ISO-8601 to reschedule. Only future scheduled posts can be updated."}},"additionalProperties":false,"description":"Fields on the post itself. At least one field across the whole request must change."},"media":{"type":"array","items":{"type":"object","properties":{"artifactId":{"type":"string","format":"uuid","description":"UUID of a verified artifact returned by POST /api/v1/uploads/complete."},"alt":{"type":"string","minLength":1,"maxLength":1000,"description":"Accessibility alt text for the media item. For images this is strongly recommended."}},"required":["artifactId"],"additionalProperties":false},"maxItems":20,"description":"Replacement media array. Replaces all existing media. Max 20 items."},"providerOptions":{"type":"object","properties":{"x":{"type":"object","additionalProperties":{}},"tiktok":{"type":"object","additionalProperties":{}},"pinterest":{"type":"object","additionalProperties":{}}},"additionalProperties":false,"description":"Per-provider overrides. Replaces existing override object."}},"additionalProperties":false},"UploadPresignEnvelope":{"type":"object","properties":{"data":{"type":"object","properties":{"artifactId":{"type":"string","format":"uuid","description":"Reserved artifact id. Reuse this id in POST /api/v1/uploads/complete."},"uploadUrl":{"type":"string","format":"uri","description":"Presigned PUT target. Send exactly one PUT with the file bytes and the listed requiredHeaders."},"requiredHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"HTTP headers the caller must send on the PUT (e.g. content-type). Keys are lowercase header names."}},"required":["artifactId","uploadUrl","requiredHeaders"],"additionalProperties":false}},"required":["data"],"additionalProperties":false},"CreateUploadRequest":{"type":"object","properties":{"fileName":{"type":"string","minLength":1,"maxLength":255,"description":"Original file name including extension."},"mimeType":{"type":"string","minLength":1,"maxLength":255,"description":"IANA media type the upload will be stored as, e.g. image/png or video/mp4."},"fileSize":{"type":"integer","exclusiveMinimum":0,"maximum":157286400,"description":"File size in bytes. Must match the byte count PUT to uploadUrl. Max 150 MB."}},"required":["fileName","mimeType","fileSize"],"additionalProperties":false},"ArtifactEnvelope":{"type":"object","properties":{"data":{"type":"object","properties":{"artifactId":{"type":"string","format":"uuid","description":"Verified artifact id. Reference it from post media."},"storageKey":{"type":"string","description":"Internal storage object key. Opaque to callers."},"url":{"type":"string","format":"uri","description":"Temporary public read URL for the verified object."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the artifact was verified."},"kind":{"type":"string","enum":["image","video"],"description":"Detected artifact kind: image or video."},"mimeType":{"type":"string","description":"Verified IANA media type of the stored object."},"sizeBytes":{"type":"integer","minimum":0,"description":"Verified byte count of the stored object."}},"required":["artifactId","storageKey","url","createdAt","kind","mimeType","sizeBytes"],"additionalProperties":false}},"required":["data"],"additionalProperties":false},"CompleteUploadRequest":{"type":"object","properties":{"artifactId":{"type":"string","format":"uuid","description":"Reserved artifact id returned by POST /api/v1/uploads. Must reference an upload whose bytes have been PUT to uploadUrl."}},"required":["artifactId"],"additionalProperties":false},"PublishingLogResponse":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique log entry id."},"deliveryId":{"type":"string","format":"uuid","description":"Delivery this event belongs to. One post has one delivery per target profile."},"postId":{"type":"string","format":"uuid","description":"Post this delivery belongs to."},"provider":{"type":"string","enum":["x","tiktok","pinterest"],"description":"Social provider the delivery targeted."},"type":{"type":"string","enum":["pending","published","failed","canceled"],"description":"Delivery event type. pending = accepted/waiting; published = delivered; failed = delivery error (see errorMessage); canceled = stopped before delivery."},"occurredAt":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the event was recorded."},"postBody":{"type":"string","description":"Snapshot of post body at event time, when relevant."},"postScheduledAt":{"type":"string","format":"date-time","description":"Snapshot of scheduledAt, when relevant."},"profileName":{"type":"string","description":"Profile label at event time."},"releaseUrl":{"type":"string","format":"uri","description":"Public URL of the published post at the provider, when available."},"providerPostId":{"type":"string","description":"Provider-native id of the published post, when available."},"errorMessage":{"type":"string","description":"Human-readable failure detail. Present only when type is failed."}},"required":["id","deliveryId","postId","provider","type","occurredAt"],"additionalProperties":false},"description":"Chronological delivery events, newest first."}},"required":["data"],"additionalProperties":false}},"parameters":{}},"paths":{"/api/v1/profiles":{"get":{"tags":["Profiles"],"operationId":"listProfiles","summary":"List connected profiles","description":"Returns all active publishing profiles visible to the authenticated API key. Use the returned profile ids when creating posts. Results are filtered by API-key scope if one is in use.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of profiles available for posting.","content":{"application/json":{"example":{"data":[{"id":"4339a6bc-9cd3-455e-8f91-df8bc63f12d7","name":"PostMantis on X","provider":"x","status":"active","username":"postmantis","postsCount":12,"createdAt":"2026-05-05T10:00:00.000Z","updatedAt":"2026-05-05T10:00:00.000Z"}]},"schema":{"$ref":"#/components/schemas/ProfilesListResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/profiles/{id}":{"get":{"tags":["Profiles"],"operationId":"getProfile","summary":"Get a single profile","description":"Returns one profile by id, including connection status and publish history. Returns 404 if the profile is not visible to the caller's API-key scope.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Profile UUID"},"required":true,"description":"Profile UUID","name":"id","in":"path"}],"responses":{"200":{"description":"Profile detail.","content":{"application/json":{"example":{"data":{"id":"4339a6bc-9cd3-455e-8f91-df8bc63f12d7","name":"PostMantis on X","provider":"x","status":"active","username":"postmantis","pictureUrl":"https://example.com/avatar.png","postsCount":12,"createdAt":"2026-05-05T10:00:00.000Z","updatedAt":"2026-05-05T10:00:00.000Z"}},"schema":{"$ref":"#/components/schemas/ProfileEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Profile not found or not accessible.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/not-found","title":"Not found","status":404,"detail":"Resource not found.","code":"notFound"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/profiles/{id}/disconnect":{"post":{"tags":["Profiles"],"operationId":"disconnectProfile","summary":"Disconnect a profile","description":"Permanently disconnects a social profile. Existing scheduled posts targeting this profile will fail at delivery time. Requires write permission.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Profile UUID"},"required":true,"description":"Profile UUID","name":"id","in":"path"}],"responses":{"204":{"description":"Profile disconnected successfully."},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Profile not found or not accessible.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/not-found","title":"Not found","status":404,"detail":"Resource not found.","code":"notFound"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/posts":{"get":{"tags":["Posts"],"operationId":"listPosts","summary":"List posts","description":"Returns posts visible to the authenticated caller, with optional filtering by status, profile, provider, and scheduled date range. Supports paginated iteration.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["pending","settled","canceled","scheduled"],"description":"Filter by post status: pending, scheduled, settled, or canceled."},"required":false,"description":"Filter by post status: pending, scheduled, settled, or canceled.","name":"status","in":"query"},{"schema":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Comma-separated list of profile UUIDs to filter by."},"required":false,"description":"Comma-separated list of profile UUIDs to filter by.","name":"profileIds","in":"query"},{"schema":{"type":"array","items":{"type":"string","enum":["x","tiktok","pinterest"]},"description":"Comma-separated list of provider names to filter by (e.g. x,linkedin)."},"required":false,"description":"Comma-separated list of provider names to filter by (e.g. x,linkedin).","name":"providers","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Only posts scheduled at or after this ISO-8601 time."},"required":false,"description":"Only posts scheduled at or after this ISO-8601 time.","name":"scheduledAfter","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Only posts scheduled at or before this ISO-8601 time."},"required":false,"description":"Only posts scheduled at or before this ISO-8601 time.","name":"scheduledBefore","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Zero-based page index."},"required":false,"description":"Zero-based page index.","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Page size, 1-100. Default 20."},"required":false,"description":"Page size, 1-100. Default 20.","name":"perPage","in":"query"}],"responses":{"200":{"description":"Paginated posts list.","content":{"application/json":{"example":{"data":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","body":"Ship small. Learn fast.","status":"settled","createdAt":"2026-05-05T10:00:00.000Z","updatedAt":"2026-05-05T10:00:00.000Z","media":[],"deliveries":[{"profileId":"4339a6bc-9cd3-455e-8f91-df8bc63f12d7","provider":"x","profileName":"PostMantis on X","status":"published","publishedAt":"2026-05-05T10:01:00.000Z","providerPostId":"1234567890","url":"https://x.com/postmantis/status/1234567890","providerOptions":{}}]}],"pagination":{"page":0,"perPage":20,"total":1}},"schema":{"$ref":"#/components/schemas/PaginatedPostsResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}},"post":{"tags":["Posts"],"operationId":"createPost","summary":"Create a post","description":"Creates a publish-now post when `post.scheduledAt` is omitted, or a scheduled post when `post.scheduledAt` is a future ISO-8601 datetime. Accepts JSON or multipart/form-data (for direct file uploads). Posts must have a body, media, or both. Use `/uploads` first if you want server-verified artifacts.","security":[{"BearerAuth":[]}],"requestBody":{"description":"Post creation payload","content":{"application/json":{"examples":{"publishTextNow":{"summary":"Publish now (text only)","value":{"post":{"body":"Ship small. Learn fast."},"profiles":["4339a6bc-9cd3-455e-8f91-df8bc63f12d7"]}},"scheduledText":{"summary":"Schedule text post","value":{"post":{"body":"Scheduled launch note.","scheduledAt":"2026-05-06T15:00:00.000Z"},"profiles":["4339a6bc-9cd3-455e-8f91-df8bc63f12d7"]}},"withMedia":{"summary":"Post with uploaded media","value":{"post":{"body":"Check out our new release."},"profiles":["4339a6bc-9cd3-455e-8f91-df8bc63f12d7"],"media":[{"artifactId":"b2c3d4e5-f6a7-8901-bcde-f23456789012","alt":"Product screenshot"}]}}},"schema":{"$ref":"#/components/schemas/CreatePostRequest"}},"multipart/form-data":{"description":"Direct file upload without a separate /uploads call. Send exactly one `payload` text field containing JSON, plus one File part per referenced file. In `payload`, reference each file by `media[].fileField` (the part name); the server uploads the bytes and replaces it with a real artifactId. All other fields match the JSON schema.","examples":{"multipartWithFile":{"summary":"Multipart: payload JSON + a file part","value":{"post":{"body":"Shipping today."},"profiles":["4339a6bc-9cd3-455e-8f91-df8bc63f12d7"],"media":[{"fileField":"image1","alt":"Product screenshot"}]}}},"schema":{"$ref":"#/components/schemas/CreatePostRequest"}}}},"responses":{"201":{"description":"Post created and accepted into delivery queue.","headers":{"Location":{"description":"URL of the newly created post.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostEnvelope"}}}},"400":{"description":"Invalid post request.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/invalid-request","title":"Invalid request","status":400,"detail":"The request payload failed validation.","code":"invalidRequest"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"415":{"description":"Unsupported content type.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unsupported-media-type","title":"Unsupported media type","status":415,"detail":"Unsupported content type. Use application/json or multipart/form-data.","code":"unsupportedMediaType"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/posts/{postId}":{"get":{"tags":["Posts"],"operationId":"getPost","summary":"Get a single post","description":"Returns full post state including media processing status and per-provider delivery outcomes. Omitted fields mean no value. Use this to poll delivery status after creation.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Post UUID"},"required":true,"description":"Post UUID","name":"postId","in":"path"}],"responses":{"200":{"description":"Post detail.","content":{"application/json":{"example":{"data":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","body":"Ship small. Learn fast.","status":"settled","createdAt":"2026-05-05T10:00:00.000Z","updatedAt":"2026-05-05T10:00:00.000Z","media":[],"deliveries":[{"profileId":"4339a6bc-9cd3-455e-8f91-df8bc63f12d7","provider":"x","profileName":"PostMantis on X","status":"published","publishedAt":"2026-05-05T10:01:00.000Z","providerPostId":"1234567890","url":"https://x.com/postmantis/status/1234567890","providerOptions":{}}]}},"schema":{"$ref":"#/components/schemas/PostEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Post not found or not accessible.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/not-found","title":"Not found","status":404,"detail":"Resource not found.","code":"notFound"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}},"patch":{"tags":["Posts"],"operationId":"updatePost","summary":"Update a scheduled post","description":"Edits a future scheduled post before delivery starts. Only `scheduled` posts with a future `scheduledAt` can be updated. Set `post.scheduledAt: null` to publish immediately. Requires write permission.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Post UUID"},"required":true,"description":"Post UUID","name":"postId","in":"path"}],"requestBody":{"description":"Fields to update. At least one field must be provided.","content":{"application/json":{"examples":{"reschedule":{"summary":"Reschedule post","value":{"post":{"scheduledAt":"2026-05-07T09:00:00.000Z"}}},"publishScheduledPostNow":{"summary":"Publish scheduled post now","value":{"post":{"scheduledAt":null}}},"editBody":{"summary":"Edit body text","value":{"post":{"body":"Updated launch note."}}}},"schema":{"$ref":"#/components/schemas/UpdatePostRequest"}}}},"responses":{"200":{"description":"Post updated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostEnvelope"}}}},"400":{"description":"Invalid update request.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/invalid-request","title":"Invalid request","status":400,"detail":"The request payload failed validation.","code":"invalidRequest"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Post not found or not accessible.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/not-found","title":"Not found","status":404,"detail":"Resource not found.","code":"notFound"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Only future scheduled posts can be edited.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/conflict","title":"Conflict","status":409,"detail":"Resource is in a state that prevents this operation.","code":"conflict"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/posts/{postId}/cancel":{"post":{"tags":["Posts"],"operationId":"cancelPost","summary":"Cancel a scheduled post","description":"Cancels a scheduled post and its pending deliveries. Only posts in `scheduled` status that have not started delivery can be canceled. Requires write permission.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Post UUID"},"required":true,"description":"Post UUID","name":"postId","in":"path"}],"responses":{"200":{"description":"Post canceled successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Post not found or not accessible.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/not-found","title":"Not found","status":404,"detail":"Resource not found.","code":"notFound"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Post is not in a cancelable state.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/conflict","title":"Conflict","status":409,"detail":"Resource is in a state that prevents this operation.","code":"conflict"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/uploads":{"post":{"tags":["Uploads"],"operationId":"createUpload","summary":"Request an upload URL","description":"Starts a two-phase upload. Returns reserved artifact id and presigned PUT URL. Caller uploads bytes directly to storage, then calls `POST /uploads/complete` with artifact id. Max file size is 150 MB.","security":[{"BearerAuth":[]}],"requestBody":{"description":"Metadata for the file to upload. The returned artifactId is reserved but not usable until /uploads/complete succeeds.","content":{"application/json":{"example":{"fileName":"launch-video.mp4","mimeType":"video/mp4","fileSize":12345678},"schema":{"$ref":"#/components/schemas/CreateUploadRequest"}}}},"responses":{"201":{"description":"Presigned upload URL created.","content":{"application/json":{"example":{"data":{"artifactId":"b2c3d4e5-f6a7-8901-bcde-f23456789012","uploadUrl":"https://storage.example.com/...","requiredHeaders":{"content-type":"video/mp4"}}},"schema":{"$ref":"#/components/schemas/UploadPresignEnvelope"}}}},"400":{"description":"Invalid upload request.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/invalid-request","title":"Invalid request","status":400,"detail":"The request payload failed validation.","code":"invalidRequest"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"415":{"description":"Unsupported content type.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unsupported-media-type","title":"Unsupported media type","status":415,"detail":"Unsupported content type. Use application/json or multipart/form-data.","code":"unsupportedMediaType"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/uploads/complete":{"post":{"tags":["Uploads"],"operationId":"completeUpload","summary":"Finalize an upload","description":"Verifies the uploaded object, promotes it to a verified artifact, and returns the artifact id and a temporary read URL. Use the artifact id in post media references.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"example":{"artifactId":"b2c3d4e5-f6a7-8901-bcde-f23456789012"},"schema":{"$ref":"#/components/schemas/CompleteUploadRequest"}}}},"responses":{"200":{"description":"Upload verified and artifact created.","content":{"application/json":{"example":{"data":{"artifactId":"b2c3d4e5-f6a7-8901-bcde-f23456789012","storageKey":"user-123/...","url":"https://cdn.example.com/...","createdAt":"2026-05-05T10:00:00.000Z","kind":"video","mimeType":"video/mp4","sizeBytes":12345678}},"schema":{"$ref":"#/components/schemas/ArtifactEnvelope"}}}},"400":{"description":"Invalid artifactId, or no matching upload found / bytes not yet uploaded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/invalid-request","title":"Invalid request","status":400,"detail":"The request payload failed validation.","code":"invalidRequest"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"API key lacks write permission.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/forbidden","title":"Forbidden","status":403,"detail":"The authenticated API key is not allowed to perform this action.","code":"forbidden"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/publish/logs":{"get":{"tags":["Publishing Logs"],"operationId":"listPublishingLogs","summary":"List publishing log entries","description":"Returns chronological delivery status history (pending, published, failed, canceled). Pending means accepted and waiting for dispatch or scheduled time.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":["integer","null"],"minimum":0,"maximum":100,"default":50,"description":"Max entries to return, 0-100. Default 50."},"required":false,"description":"Max entries to return, 0-100. Default 50.","name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Zero-based offset for pagination. Default 0."},"required":false,"description":"Zero-based offset for pagination. Default 0.","name":"offset","in":"query"}],"responses":{"200":{"description":"Publishing log entries.","content":{"application/json":{"examples":{"published":{"summary":"Successful delivery","value":{"data":[{"id":"c3d4e5f6-a7b8-9012-cdef-345678901234","deliveryId":"d4e5f6a7-b8c9-0123-defa-456789012345","postId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","postBody":"Ship small. Learn fast.","provider":"x","profileName":"PostMantis on X","type":"published","occurredAt":"2026-05-05T10:01:00.000Z","releaseUrl":"https://x.com/postmantis/status/1234567890","providerPostId":"1234567890"}]}},"failed":{"summary":"Failed delivery with error detail","value":{"data":[{"id":"e5f6a7b8-c9d0-1234-efab-567890123456","deliveryId":"f6a7b8c9-d0e1-2345-fabc-678901234567","postId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","postBody":"Ship small. Learn fast.","provider":"x","profileName":"PostMantis on X","type":"failed","occurredAt":"2026-05-05T10:01:05.000Z","errorMessage":"Provider rate limit exceeded. Retry via post creation."}]}}},"schema":{"$ref":"#/components/schemas/PublishingLogResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required.","code":"unauthorized"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"example":{"type":"https://postmantis.com/problems/rate-limited","title":"Too many requests","status":429,"detail":"Too many requests.","code":"rateLimited"},"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}}},"webhooks":{}}