{"openapi":"3.1.0","info":{"title":"Content Studio API","version":"1.0.0","summary":"Connect any website or platform to Content Studio and publish the team's articles on it.","description":"Server-to-server API. A site pulls finished articles (deliveries), publishes them its own way and acknowledges each one with the live link. Signed webhooks tell the site when something is waiting; polling `GET /deliveries` works as a fallback. The site also reports its existing pages, comments and taxonomies so the team can improve what is already there.\n\nAuthentication: `Authorization: Bearer cst_…` with a per-project token created in the project's settings, or obtained through the one-click connect flow (OAuth 2 authorization code + PKCE). Tokens are never accepted in a query string. No CORS headers are sent: never call this API from a browser.\n\nErrors: `{ \"error\": { \"code\", \"message\", \"requestId\", \"details?\" } }`. `code` is stable English; `message` is Persian, for people.\n\nRate limit: 120 requests per minute per token, reported with `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; a `429` carries `Retry-After`.\n\nPersian guide: /docs/api","contact":{"name":"Content Studio","url":"/docs/api"}},"servers":[{"url":"https://content.binvest.finance/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Meta","description":"API information and the WordPress plugin."},{"name":"Connect","description":"One-click connect: trade an authorization code for a token."},{"name":"Account","description":"The token and the project it belongs to."},{"name":"Posts","description":"The team's articles and their pictures."},{"name":"Deliveries","description":"Work the site pulls, applies and acknowledges."},{"name":"Site","description":"What the site reports about itself."},{"name":"Webhooks","description":"Where and how the studio signals the site."}],"paths":{"/":{"get":{"tags":["Meta"],"operationId":"getApiInfo","summary":"API name, version and documentation links","security":[],"responses":{"200":{"description":"API information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiInfo"}}}}}}},"/openapi.json":{"get":{"tags":["Meta"],"operationId":"getOpenApi","summary":"This OpenAPI document","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/plugins/wordpress/latest":{"get":{"tags":["Meta"],"operationId":"getWordPressPlugin","summary":"Latest WordPress plugin version and download address","description":"Used by the plugin's own updater.","security":[],"responses":{"200":{"description":"Plugin information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluginInfo"}}}}}}},"/connect/token":{"post":{"tags":["Connect"],"operationId":"exchangeConnectCode","summary":"Trade a one-click connect code for a token","description":"Step 3 of one-click connect. Send the `code` the studio redirected back with, the original `code_verifier` and the exact `redirect_uri`. A code works once and expires after 5 minutes. Optionally register a webhook URL in the same call; its secret is returned once.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExchangeRequest"}}}},"responses":{"200":{"description":"Token issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExchangeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/me":{"get":{"tags":["Account"],"operationId":"getMe","summary":"Check a token: its project, scopes and connection","description":"The cheapest way to test a connection. Works with any scope.","responses":{"200":{"description":"Token information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Account"],"operationId":"revokeOwnToken","summary":"Revoke the token making this call","description":"For a site that disconnects itself. If this token is the project's publishing connection, the connection ends too. Works with any scope.","responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/posts":{"get":{"tags":["Posts"],"operationId":"listPosts","summary":"List the team's articles, newest first","x-scopes":["posts:read"],"parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/PostStatus"}},{"$ref":"#/components/parameters/Limit"},{"name":"cursor","in":"query","description":"The `nextCursor` of the previous page (an article slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of articles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/posts/{slug}":{"get":{"tags":["Posts"],"operationId":"getPost","summary":"One article with its full HTML","x-scopes":["posts:read"],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"description":"The article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Post"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/posts/{slug}/cover":{"get":{"tags":["Posts"],"operationId":"getPostCover","summary":"An article's cover image (binary)","x-scopes":["posts:read"],"parameters":[{"$ref":"#/components/parameters/Slug"}],"responses":{"200":{"description":"The image","content":{"image/png":{},"image/jpeg":{},"image/webp":{},"image/gif":{}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/figures/{key}":{"get":{"tags":["Posts"],"operationId":"getFigure","summary":"A picture inside an article (binary)","description":"Article HTML points its <img> tags here. Download each one, host it yourself and replace the address in the HTML.","x-scopes":["posts:read"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{12}$"}}],"responses":{"200":{"description":"The image","content":{"image/png":{},"image/jpeg":{},"image/webp":{},"image/gif":{}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/deliveries":{"get":{"tags":["Deliveries"],"operationId":"listDeliveries","summary":"Deliveries waiting for the site (oldest first)","description":"`status=pending` (the default) lists what is ready to take now: new deliveries, retries whose wait is over, and leases that ran out.","x-scopes":["deliveries:read"],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","acked","failed","cancelled","all"],"default":"pending"}},{"$ref":"#/components/parameters/Limit"},{"name":"cursor","in":"query","description":"The `nextCursor` of the previous page (a delivery id).","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/deliveries/{id}":{"get":{"tags":["Deliveries"],"operationId":"getDelivery","summary":"Fetch a delivery and lease it for 15 minutes","description":"Fetching a pending delivery leases it to you for 15 minutes so another worker doesn't take it too. Acknowledge or fail it within the lease; otherwise it returns to the queue. The payload is built at fetch time, so it always holds the latest text.","x-scopes":["deliveries:read"],"parameters":[{"$ref":"#/components/parameters/DeliveryId"}],"responses":{"200":{"description":"The delivery and its payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"The article or proposal behind this delivery is gone; the delivery was cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/deliveries/{id}/ack":{"post":{"tags":["Deliveries"],"operationId":"ackDelivery","summary":"Report a delivery done","description":"Idempotent: acknowledging the same delivery again returns `duplicate: true` and changes nothing.","x-scopes":["deliveries:write"],"parameters":[{"$ref":"#/components/parameters/DeliveryId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckRequest"}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/deliveries/{id}/fail":{"post":{"tags":["Deliveries"],"operationId":"failDelivery","summary":"Report a delivery that could not be done","description":"`retryable: true` puts it back in the queue after a growing wait (up to 5 attempts). `snapshot_mismatch` on a content change is never retried: the page changed since the proposal was written.","x-scopes":["deliveries:write"],"parameters":[{"$ref":"#/components/parameters/DeliveryId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailRequest"}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/site":{"get":{"tags":["Site"],"operationId":"getSite","summary":"The connection as the studio sees it","x-scopes":["project:read"],"responses":{"200":{"description":"Connection and sync totals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteView"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"put":{"tags":["Site"],"operationId":"putSite","summary":"Register this site as the project's connection","description":"Makes this token the project's publishing connection and records what the site is. Call it once after connecting and again when the platform, plugin version or publish mode changes.","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteInfo"}}}},"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/Connection"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/content":{"put":{"tags":["Site"],"operationId":"putSiteContent","summary":"Report the site's pages and posts (upsert, up to 100 per call)","description":"Send every published post and page once, then each one again whenever it changes. `deleted: true` removes one. The studio computes each item's hash as `sha256(title + \"\\0\" + excerpt + \"\\0\" + contentHtml)` over the raw stored values. Body limit: 4 MB.","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/SiteContentItem"}},"reset":{"type":"boolean","default":false,"description":"Start a full sync: everything the studio kept for this site is replaced by what this and the following calls send. Send it with the first batch only."}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/TooLarge"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/comments":{"put":{"tags":["Site"],"operationId":"putSiteComments","summary":"Report readers' comments (upsert, up to 200 per call)","description":"Only a display name and the text are kept. Email addresses and IPs are ignored if sent; don't send them.","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/SiteComment"}},"reset":{"type":"boolean","default":false,"description":"Start a full sync: everything the studio kept for this site is replaced by what this and the following calls send. Send it with the first batch only."}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Invalid"}}}},"/site/taxonomies":{"put":{"tags":["Site"],"operationId":"putSiteTaxonomies","summary":"Replace the site's categories and tags","x-scopes":["site:write"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/Term"}},"tags":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/Term"}}}}}}},"responses":{"200":{"description":"Totals kept","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"integer"},"tags":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/webhook":{"get":{"tags":["Webhooks"],"operationId":"getWebhook","summary":"The webhook address and its last result","x-scopes":["webhooks:manage"],"responses":{"200":{"description":"Webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookState"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"put":{"tags":["Webhooks"],"operationId":"putWebhook","summary":"Set the webhook address","description":"Must be https and on the project's own domain. The signing secret is returned only when it is created; keep it. Use `/webhook/rotate` for a new one.","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Webhook set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"}}},"delete":{"tags":["Webhooks"],"operationId":"deleteWebhook","summary":"Stop sending webhooks (polling still works)","x-scopes":["webhooks:manage"],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/webhook/rotate":{"post":{"tags":["Webhooks"],"operationId":"rotateWebhookSecret","summary":"Replace the signing secret","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"New secret (shown once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/webhook/test":{"post":{"tags":["Webhooks"],"operationId":"testWebhook","summary":"Send a signed `ping` event now","x-scopes":["webhooks:manage"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"What the site answered","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"integer"},"error":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"}}}}},"webhooks":{"delivery.created":{"post":{"summary":"A delivery is waiting","description":"Answer 2xx within 10 seconds and do the work afterwards: fetch `GET /deliveries/{deliveryId}`. Verify `X-Studio-Signature` first. Failed calls are retried after 1 min, 5 min, 30 min, 2 h and 6 h; keep polling as a fallback.","parameters":[{"$ref":"#/components/parameters/SignatureHeader"},{"$ref":"#/components/parameters/EventHeader"},{"$ref":"#/components/parameters/EventIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}}}},"ping":{"post":{"summary":"A test from the owner's settings or `POST /webhook/test`","parameters":[{"$ref":"#/components/parameters/SignatureHeader"},{"$ref":"#/components/parameters/EventHeader"},{"$ref":"#/components/parameters/EventIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}}}},"connection.revoked":{"post":{"summary":"The owner disconnected the site or revoked its token","description":"Sent once, not retried. Forget the token and webhook secret.","parameters":[{"$ref":"#/components/parameters/SignatureHeader"},{"$ref":"#/components/parameters/EventHeader"},{"$ref":"#/components/parameters/EventIdHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"cst_<id>_<secret>","description":"A project API token. Send it in the Authorization header only."}},"parameters":{"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},"DeliveryId":{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^dlv_[0-9a-f]{20}$"}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","description":"8–100 characters. The same key from the same token returns the first answer again for 24 hours.","schema":{"type":"string","pattern":"^[A-Za-z0-9._:-]{8,100}$"}},"SignatureHeader":{"name":"X-Studio-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>`. Reject if the timestamp is more than 300 s away from now.","schema":{"type":"string"}},"EventHeader":{"name":"X-Studio-Event","in":"header","required":true,"schema":{"type":"string","enum":["delivery.created","ping","connection.revoked"]}},"EventIdHeader":{"name":"X-Studio-Event-Id","in":"header","required":true,"description":"Unique per event; a retry keeps the same id. Use it to ignore duplicates.","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, invalid, revoked or expired token (`missing_token`, `invalid_token`, `token_revoked`, `token_expired`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The token lacks the scope (`insufficient_scope`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"The thing is in a state that does not allow this (`delivery_closed`, `webhook_missing`, `connection_exists`: the project's site connection belongs to another live token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Invalid":{"description":"A field is invalid (`invalid_field`, `site_mismatch`, `invalid_webhook_url`, `too_many_items`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooLarge":{"description":"Body too large (`payload_too_large`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests (`rate_limited`, `too_many_failures`)","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string","examples":["invalid_token"]},"message":{"type":"string","description":"Persian, for people"},"requestId":{"type":"string","examples":["req_1a2b3c4d5e6f7a8b"]},"details":{"type":"object","additionalProperties":true}}}}},"ApiInfo":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"docs":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"}}},"PluginInfo":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"requires":{"type":"string"},"requiresPhp":{"type":"string"},"tested":{"type":"string"},"downloadUrl":{"type":"string","format":"uri"},"homepage":{"type":"string","format":"uri"}}},"Scope":{"type":"string","enum":["project:read","posts:read","deliveries:read","deliveries:write","site:write","webhooks:manage"]},"TokenExchangeRequest":{"type":"object","required":["code","code_verifier","redirect_uri"],"properties":{"code":{"type":"string"},"code_verifier":{"type":"string","minLength":43,"maxLength":128},"redirect_uri":{"type":"string","format":"uri","description":"Exactly the one sent to /connect"},"webhook_url":{"type":"string","format":"uri","description":"Optional: register the webhook in the same call"}}},"TokenExchangeResponse":{"type":"object","properties":{"access_token":{"type":"string","description":"Shown once. Store it encrypted."},"token_type":{"type":"string","const":"Bearer"},"scope":{"type":"string","description":"Space-separated scopes"},"project":{"$ref":"#/components/schemas/Project"},"webhook":{"oneOf":[{"$ref":"#/components/schemas/WebhookSecret"},{"type":"null"}]}}},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"siteUrl":{"type":"string","format":"uri"},"status":{"type":"string"},"dailyPosts":{"type":"integer"},"timezone":{"type":"string"}}},"Me":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"},"token":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"kind":{"type":"string","enum":["manual","connect"]}}},"connection":{"$ref":"#/components/schemas/Connection"},"apiVersion":{"type":"string"}}},"Connection":{"type":"object","properties":{"connected":{"type":"boolean"},"platform":{"type":"string"},"siteUrl":{"type":"string"},"platformVersion":{"type":"string"},"pluginVersion":{"type":"string"},"seoPlugin":{"type":"string"},"publishMode":{"type":"string"},"connectedAt":{"type":"integer","description":"ms since epoch"},"lastSeenAt":{"type":"integer","description":"ms since epoch"},"tokenName":{"type":"string"},"webhook":{"type":"object","properties":{"configured":{"type":"boolean"},"lastAt":{"type":"integer"},"ok":{"type":["boolean","null"]},"status":{"type":"integer"}}},"health":{"type":"string","enum":["ok","quiet","webhook_failing"]}}},"PostStatus":{"type":"string","enum":["reviewing","ready","queued","draft","scheduled","published"]},"PostSummary":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"keyword":{"type":"string"},"excerpt":{"type":"string"},"status":{"$ref":"#/components/schemas/PostStatus"},"remoteId":{"type":["string","null"],"description":"The site's own id once it has the article"},"link":{"type":["string","null"],"format":"uri"},"hasCover":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"publishedAt":{"type":["string","null"],"format":"date-time"}}},"Post":{"allOf":[{"$ref":"#/components/schemas/PostSummary"},{"type":"object","properties":{"contentHtml":{"type":"string","description":"Article body. The title is not repeated as an <h1>; print it as the page's only H1."},"seo":{"$ref":"#/components/schemas/Seo"},"cover":{"oneOf":[{"$ref":"#/components/schemas/Image"},{"type":"null"}]},"figures":{"type":"array","items":{"$ref":"#/components/schemas/Figure"}}}}]},"PostPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PostSummary"}},"nextCursor":{"type":["string","null"]}}},"Seo":{"type":"object","properties":{"title":{"type":"string","description":"Empty for a new post: keep the site's own title template"},"description":{"type":"string","description":"Meta description"},"focusKeyword":{"type":"string"}}},"Image":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Needs the same Bearer token"},"mime":{"type":"string"},"alt":{"type":"string"},"version":{"type":"integer","description":"Changes when the picture changes"}}},"Figure":{"type":"object","properties":{"key":{"type":"string"},"url":{"type":"string","format":"uri","description":"Appears verbatim in contentHtml; replace it with your own copy"},"mime":{"type":"string"},"alt":{"type":"string"}}},"DeliveryType":{"type":"string","enum":["post.publish","post.cover","content.update","content.restore"]},"DeliverySummary":{"type":"object","properties":{"id":{"type":"string"},"type":{"$ref":"#/components/schemas/DeliveryType"},"status":{"type":"string","enum":["pending","delivered","acked","failed","cancelled"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"attempts":{"type":"integer"},"lastError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"DeliveryPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeliverySummary"}},"nextCursor":{"type":["string","null"]}}},"Delivery":{"type":"object","properties":{"id":{"type":"string"},"type":{"$ref":"#/components/schemas/DeliveryType"},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"leaseUntil":{"type":["string","null"],"format":"date-time"},"attempts":{"type":"integer"},"payload":{"oneOf":[{"$ref":"#/components/schemas/PublishPayload"},{"$ref":"#/components/schemas/CoverPayload"},{"$ref":"#/components/schemas/ContentPayload"}]}}},"PublishPayload":{"type":"object","description":"post.publish: create the article (or update it, when remoteId is set). Look it up by slug first so a retried delivery never creates a second copy.","properties":{"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string"},"keyword":{"type":"string"},"contentHtml":{"type":"string"},"seo":{"$ref":"#/components/schemas/Seo"},"cover":{"oneOf":[{"$ref":"#/components/schemas/Image"},{"type":"null"}]},"figures":{"type":"array","items":{"$ref":"#/components/schemas/Figure"}},"remoteId":{"type":["string","null"]}}},"CoverPayload":{"type":"object","description":"post.cover: set a new featured image on an article the site already has.","properties":{"slug":{"type":"string"},"remoteId":{"type":"string"},"cover":{"$ref":"#/components/schemas/Image"}}},"ContentPayload":{"type":"object","description":"content.update / content.restore: rewrite an existing page the owner approved. Apply only if the page's current hash equals expectedHash; otherwise fail with code snapshot_mismatch.","properties":{"remoteId":{"type":"string"},"type":{"type":"string"},"url":{"type":"string","format":"uri"},"expectedHash":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string"},"contentHtml":{"type":"string","description":"Media the page had and the rewrite dropped is already put back at the end"},"seo":{"$ref":"#/components/schemas/Seo"},"figures":{"type":"array","items":{"$ref":"#/components/schemas/Figure"}}}},"AckRequest":{"type":"object","properties":{"remoteId":{"type":"string","description":"The site's id for the article or page (required except for post.cover)"},"link":{"type":"string","format":"uri","description":"Public address; required when status is publish for post.publish. Must be on the project's domain."},"status":{"type":"string","enum":["publish","draft","future","pending","private"],"default":"publish"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"Hash of the stored page after the change"},"modified":{"type":"string","description":"When the site last changed it"},"coverSet":{"type":"boolean","description":"The cover in the payload is now the featured image"},"media":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional: figure key → your hosted address"}}},"AckResponse":{"type":"object","properties":{"delivery":{"$ref":"#/components/schemas/DeliverySummary"},"duplicate":{"type":"boolean"}}},"FailRequest":{"type":"object","properties":{"code":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","examples":["snapshot_mismatch","media_failed","not_found"]},"message":{"type":"string","maxLength":500},"retryable":{"type":"boolean","default":false}}},"FailResponse":{"type":"object","properties":{"delivery":{"$ref":"#/components/schemas/DeliverySummary"},"willRetry":{"type":"boolean"}}},"SiteInfo":{"type":"object","properties":{"platform":{"type":"string","examples":["wordpress","shopify","custom"]},"siteUrl":{"type":"string","format":"uri","description":"Must be on the project's domain"},"platformVersion":{"type":"string"},"pluginVersion":{"type":"string"},"seoPlugin":{"type":"string","examples":["yoast","rankmath","none"]},"publishMode":{"type":"string","enum":["publish","draft","future"]},"timezone":{"type":"string","examples":["Asia/Tehran"]},"language":{"type":"string","examples":["fa_IR"]}}},"SiteView":{"type":"object","properties":{"connection":{"$ref":"#/components/schemas/Connection"},"stats":{"type":"object","properties":{"pages":{"type":"integer"},"published":{"type":"integer"},"comments":{"type":"integer"},"syncedAt":{"type":"integer"}}},"taxonomies":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/Term"}},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Term"}}}}}},"SiteContentItem":{"type":"object","required":["remoteId"],"properties":{"remoteId":{"type":"string"},"deleted":{"type":"boolean","description":"Remove this item; nothing else is needed"},"type":{"type":"string","examples":["post","page"]},"status":{"type":"string","enum":["publish","draft","future","pending","private","trash"]},"link":{"type":"string","format":"uri"},"title":{"type":"string"},"excerpt":{"type":"string"},"contentHtml":{"type":"string","description":"Raw stored HTML (not rendered)"},"modified":{"type":"string"},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}}},"SiteComment":{"type":"object","required":["remoteId"],"properties":{"remoteId":{"type":"string"},"deleted":{"type":"boolean"},"postRemoteId":{"type":"string"},"postLink":{"type":"string","format":"uri"},"parentRemoteId":{"type":"string"},"author":{"type":"string","description":"Display name only"},"content":{"type":"string"},"date":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["approved","pending","spam","trash"]}}},"Term":{"type":"object","required":["remoteId","name"],"properties":{"remoteId":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"parent":{"type":"string"},"count":{"type":"integer"}}},"UpsertResult":{"type":"object","properties":{"accepted":{"type":"integer"},"deleted":{"type":"integer"},"rejected":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"error":{"type":"string"}}}},"total":{"type":"integer"}}},"WebhookState":{"type":"object","properties":{"configured":{"type":"boolean"},"url":{"type":"string"},"lastAt":{"type":"integer"},"ok":{"type":["boolean","null"]},"status":{"type":"integer"}}},"WebhookSecret":{"type":"object","properties":{"url":{"type":"string"},"secret":{"type":["string","null"],"description":"whsec_…, only when newly created"}}},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","examples":["evt_0123456789abcdef01234567"]},"event":{"type":"string","enum":["delivery.created","ping","connection.revoked"]},"projectId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"deliveryId":{"type":"string"},"type":{"$ref":"#/components/schemas/DeliveryType"}},"additionalProperties":true}}}}}}