{"openapi":"3.1.0","info":{"title":"ChatCMM API","version":"0.13.0","description":"REST API for ChatCMM — the AI-powered content marketing platform with WordPress integration.\n\n## Authentication\n\nAPI-key-capable endpoints require authentication via one of three methods:\n\n| Method | Header | Example |\n|--------|--------|---------|\n| **Bearer Token** | `Authorization` | `Bearer cmm_your_key` |\n| **API Key Header** | `X-API-Key` | `cmm_your_key` |\n| **Query Parameter** ⚠️ | `api_key` | `?api_key=cmm_your_key` |\n\n> **Deprecation Notice:** The `api_key` query parameter is **deprecated** and will be removed in a future version. Query parameters leak keys in server logs, browser history, and referrer headers. Use the `X-API-Key` header or `Authorization: Bearer` instead.\n\nCreate API keys in **Dashboard → Settings → API Keys**. Operations whose security is `SessionCookie` are Dashboard-session only and not usable with an API key (401).\n\n## Rate Limiting\n\n| Tier | Limit | Scope |\n|------|-------|-------|\n| **Global** | 120 req / 60 s | Per IP address |\n| **General** | 100 req / 60 s | Per IP per endpoint group |\n| **AI endpoints** | 30 req / 60 min | AI content generation & editing |\n| **Auth endpoints** | 10 req / 60 s | Login, registration |\n\n- Response headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`\n- HTTP 429 with `Retry-After: 60` when exceeded\n\n## Organization Context\n\nMulti-tenant endpoints filter by organization automatically. An API key is bound to the organization it was created in and uses that organization as its context; create a separate key for each organization. Keys owned by platform administrators keep their administrator-wide access across organizations for now. A different `X-Organization-Id` header is rejected with HTTP 403 `org_mismatch`, and an owner who has left the key's organization gets 403 `org_membership_revoked`. Legacy keys without an organization binding may send `X-Organization-Id` for an organization the owner belongs to (otherwise 403 `org_mismatch`). If the organization check itself fails (database error), the request is rejected with HTTP 503 `org_resolution_failed`. A key bound to a soft-deleted organization is rejected like a revoked membership (403 `org_membership_revoked`).\n\n## Timestamps\n\nAll timestamps are **Unix seconds** (not milliseconds).","contact":{"name":"AdSimple GmbH","url":"https://chatcmm.com","email":"support@chatcmm.com"},"license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"}},"servers":[{"url":"https://chatcmm.com","description":"Production"},{"url":"http://localhost:3137","description":"Local Development"}],"security":[{"BearerAuth":[]},{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"tags":[{"name":"Authentication","description":"API key management and verification"},{"name":"Content","description":"Blog posts and articles — create, generate, publish"},{"name":"Authors","description":"Public author identities, WordPress bylines and author boxes"},{"name":"WordPress Sites","description":"Connect and manage WordPress sites"},{"name":"Campaigns","description":"Content marketing campaigns with article planning"},{"name":"Channels","description":"Marketplace channel listings"},{"name":"Bookings","description":"Marketplace booking management"},{"name":"Media","description":"Image upload and AI generation"},{"name":"Team","description":"Team member management and invitations"},{"name":"Organizations","description":"Organization (multi-tenant) management"},{"name":"Orders","description":"Publisher orders — line items, external contributions, status lifecycle, Claribill invoicing"},{"name":"Publishing Channels","description":"WordPress and custom API publishing targets of the organization"},{"name":"Dashboard","description":"Aggregated statistics"},{"name":"Social","description":"Social posts across networks: draft, approve, schedule, publish, retry"}],"paths":{"/api/api-keys":{"get":{"tags":["Authentication"],"summary":"List API keys","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Returns all API keys for the authenticated user. Keys are shown with prefix only — the full key is never returned after creation.","operationId":"listApiKeys","responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}}}},"post":{"tags":["Authentication"],"summary":"Create API key","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Creates a new API key. The full key (`plainKey`) is returned only in this response — store it securely.","operationId":"createApiKey","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","permissions"],"properties":{"name":{"type":"string","description":"Human-readable label for the key"},"permissions":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Scopes the key is allowed to use (e.g. content:read, campaigns:write). Required and non-empty; unknown scopes are rejected. A key grants nothing beyond its listed scopes."},"expiresAt":{"type":"integer","nullable":true,"description":"Unix timestamp when the key expires. Null = never."}}}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"$ref":"#/components/schemas/ApiKeyWithPlainKey"},"message":{"type":"string"}}}}}},"400":{"description":"Missing name or missing/empty/unknown permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/{id}":{"get":{"tags":["Authentication"],"summary":"Get API key details","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"getApiKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"API key details","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"$ref":"#/components/schemas/ApiKey"}}}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Authentication"],"summary":"Update or revoke API key","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"updateApiKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"revoke":{"type":"boolean","description":"Set true to revoke the key"}}}}}},"responses":{"200":{"description":"Key updated","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"$ref":"#/components/schemas/ApiKey"},"message":{"type":"string"}}}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Authentication"],"summary":"Delete API key","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Permanently deletes an API key. This action cannot be undone.","operationId":"deleteApiKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Key deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/verify":{"post":{"tags":["Authentication"],"summary":"Verify API key","description":"Check whether an API key is valid. Send the key via the Authorization header.","operationId":"verifyApiKey","security":[{"BearerAuth":[]},{"ApiKeyHeader":[]}],"responses":{"200":{"description":"Key is valid","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","enum":[true]},"userId":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"orgId":{"type":"string","format":"uuid","nullable":true,"description":"Organization the key is bound to; null for legacy keys without a binding."}}}}}},"401":{"description":"Invalid key","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","enum":[false]},"error":{"type":"string"}}}}}},"403":{"description":"`org_membership_revoked`: the key's owner is no longer a member of the bound organization, or that organization was deleted. `orgId` names the bound organization.","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","enum":[false]},"error":{"type":"string"},"code":{"type":"string","enum":["org_membership_revoked"]},"orgId":{"type":"string","format":"uuid"}},"required":["valid","error","code","orgId"]}}}},"503":{"description":"`org_resolution_failed`: the bound organization could not be checked (temporary database error). The key is reported as not valid; retry later.","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","enum":[false]},"error":{"type":"string"},"code":{"type":"string","enum":["org_resolution_failed"]}},"required":["valid","error","code"]}}}}}}},"/api/content":{"get":{"tags":["Content"],"summary":"List content","description":"Returns blog posts and articles. Filtered by organization and user role automatically.","operationId":"listContent","x-required-scope":"content:read","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["draft","generating","review","approved","scheduled","publishing","published","failed"]}},{"name":"siteId","in":"query","schema":{"type":"integer"},"description":"Filter by WordPress site"},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"List of content items","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Content"}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Content"],"summary":"Create content","description":"Creates a new blog post or article. The content starts as a draft.","operationId":"createContent","x-required-scope":"content:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"content":{"type":"string","description":"HTML body"},"language":{"type":"string","default":"en","description":"ISO language code"},"prompt":{"type":"string","description":"AI generation prompt"},"contentLength":{"type":"string","enum":["short","medium","long"],"default":"medium"},"keywords":{"type":"array","items":{"type":"string"}},"metadata":{"type":"object"},"wordPressSiteId":{"type":"integer"},"categoryId":{"type":"integer"},"isDraft":{"type":"boolean","default":true},"status":{"type":"string","default":"draft"},"scheduledAt":{"type":"integer","description":"Unix timestamp"},"featuredImagePrompt":{"type":"string"},"featuredImageUrl":{"type":"string","format":"uri"},"metaTitle":{"type":"string"},"metaDescription":{"type":"string"},"customerUserId":{"type":"string"},"authorProfileId":{"type":"string","format":"uuid","nullable":true}}}}}},"responses":{"201":{"description":"Content created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Content"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Content quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}":{"get":{"tags":["Content"],"summary":"Get content by ID","operationId":"getContent","x-required-scope":"content:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Content item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Content"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Content"],"summary":"Update content","operationId":"updateContent","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"content":{"type":"string"},"language":{"type":"string"},"prompt":{"type":"string"},"contentLength":{"type":"string","enum":["short","medium","long"]},"keywords":{"type":"array","items":{"type":"string"}},"metadata":{"type":"object"},"wordPressSiteId":{"type":"integer"},"categoryId":{"type":"integer"},"isDraft":{"type":"boolean"},"status":{"type":"string"},"assignedTo":{"type":"string"},"authorProfileId":{"type":"string","format":"uuid","nullable":true},"scheduledAt":{"type":"integer"},"metaTitle":{"type":"string"},"metaDescription":{"type":"string"}}}}}},"responses":{"200":{"description":"Content updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Content"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Content"],"summary":"Delete content","description":"Soft-deletes the content. Optionally deletes from WordPress too.","operationId":"deleteContent","x-required-scope":"content:delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"deleteFromWordPress","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Content deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"wordPressDeleted":{"type":"boolean"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/generate":{"post":{"tags":["Content"],"summary":"Generate AI content","description":"Uses AI to generate the blog post body based on the content's prompt, keywords, and site context. Consumes AI credits.","operationId":"generateContent","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"AI model ID override"},"reasoningEffort":{"type":"string","enum":["none","low","medium","high"]},"textVerbosity":{"type":"string","enum":["low","medium","high"]}}}}}},"responses":{"200":{"description":"Content generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Content"}}}},"400":{"description":"No prompt set on the content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient AI credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/publish":{"post":{"tags":["Content"],"summary":"Publish to WordPress","description":"Publishes the content to the connected WordPress site. Creates or updates the WordPress post.","operationId":"publishContent","x-required-scope":"content:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"wordPressSiteId":{"type":"integer","description":"Override target site"},"categoryId":{"type":"integer","description":"Override target category"}}}}}},"responses":{"200":{"description":"Published successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"content":{"$ref":"#/components/schemas/Content"},"wordPressPostId":{"type":"integer"},"wordPressUrl":{"type":"string","format":"uri"}}}}}},"400":{"description":"No WordPress site configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"WordPress API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/unpublish":{"post":{"tags":["Content"],"summary":"Unpublish from WordPress","description":"Removes the post from WordPress and resets the content status to draft.","operationId":"unpublishContent","x-required-scope":"content:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Unpublished successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Content is not published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/featured-image":{"post":{"tags":["Content"],"summary":"Generate featured image","description":"Generates an AI featured image based on the content's image prompt. Optionally uploads it to WordPress.","operationId":"generateFeaturedImage","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"uploadToWordPress":{"type":"boolean","description":"Upload image to WP media library"},"model":{"type":"string","description":"Image model ID (e.g. gpt-image-1)"}}}}}},"responses":{"200":{"description":"Image generated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"imageUrl":{"type":"string","format":"uri"},"s3Url":{"type":"string","format":"uri"},"mediaId":{"type":"integer"},"wordpressMediaId":{"type":"integer"},"content":{"$ref":"#/components/schemas/Content"}}}}}},"400":{"description":"No image prompt set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/changes":{"get":{"tags":["Content"],"summary":"Change history","description":"Change history of a content item during the customer approval process, newest first.","operationId":"listContentChanges","x-required-scope":"content:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Change events","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"contentId":{"type":"integer"},"campaignArticleId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"changeType":{"type":"string"},"actorType":{"type":"string"},"actorUserId":{"type":["string","null"]},"actorEmail":{"type":["string","null"]},"actorName":{"type":["string","null"]},"source":{"type":"string"},"changedFields":{"type":"array","items":{"type":"string"}},"before":{"type":"object","additionalProperties":true},"after":{"type":"object","additionalProperties":true},"note":{"type":["string","null"]},"createdAt":{"type":"integer","description":"Unix timestamp"}}}}}}}}},"400":{"description":"Invalid content ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/generate-image":{"post":{"tags":["Content"],"summary":"Generate featured image","description":"Generates an AI featured image, stores it in the media library and sets it on the content. Optionally uploads it to the WordPress target. Billable (credits or an included revision).","operationId":"generateContentImage","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"uploadToWordPress":{"type":"boolean","default":false},"model":{"type":"string","default":"gpt-image-1-mini","description":"HD models (e.g. `gpt-image-1`) need a plan with HD images, otherwise 403 `hd_image_upgrade_required`"},"size":{"type":"string","default":"1536x1024"},"quality":{"type":"string","default":"standard","description":"`high` counts as HD (see `model`)"},"style":{"type":"string","default":"photorealistic"},"customPrompt":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated content with the new image","content":{"application/json":{"schema":{"allOf":[{"allOf":[{"$ref":"#/components/schemas/Content"},{"type":"object","properties":{"mediaId":{"type":"integer"},"s3Url":{"type":"string","format":"uri"}}}]},{"type":"object","properties":{"promptUsed":{"type":"string"},"revisedPrompt":{"type":["string","null"]},"billingType":{"type":"string","enum":["image_generation","included_image_revision","image_revision"]},"remainingIncludedRevisions":{"type":"integer"}}}]}}}},"400":{"description":"Invalid ID, no title, no image prompt or invalid target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"creditsRequired":{"type":"number"},"creditsAvailable":{"type":"number"}}}}}},"403":{"description":"HD image upgrade required or target not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Content is locked by a running remote mutation, a programme guard or a changed target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/image-prompt-preview":{"post":{"tags":["Content"],"summary":"Preview image prompt","description":"Returns the suggested image prompt and the credit cost of the next image generation. No side effects.","operationId":"previewContentImagePrompt","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"style":{"type":"string","default":"photorealistic"}}}}}},"responses":{"200":{"description":"Prompt preview","content":{"application/json":{"schema":{"type":"object","properties":{"suggestedPrompt":{"type":"string"},"generationMode":{"type":"string","enum":["initial","revision"]},"includedRevisionLimit":{"type":"integer"},"remainingIncludedRevisions":{"type":"integer"},"nextActionCreditCost":{"type":"integer"},"hdAccess":{"type":"object","properties":{"allowed":{"type":"boolean"},"reason":{"type":"string"},"tier":{"type":"string"},"requiredTier":{"type":"string"}}}}}}}},"400":{"description":"Invalid content ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/links":{"get":{"tags":["Content"],"summary":"List in-text links","operationId":"listContentLinks","x-required-scope":"content:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Links ordered by position","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"keyword":{"type":"string"},"url":{"type":"string","format":"uri"},"linkType":{"type":"string","enum":["dofollow","nofollow","sponsored"]},"titleAttribute":{"type":["string","null"]},"position":{"type":"integer"}}}}}}}}},"400":{"description":"Invalid content ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Content"],"summary":"Replace in-text links","description":"Replaces all keyword links of the content (at most 3).","operationId":"saveContentLinks","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["links"],"properties":{"links":{"type":"array","maxItems":3,"items":{"type":"object","required":["keyword","url"],"properties":{"keyword":{"type":"string","minLength":2},"url":{"type":"string","format":"uri"},"linkType":{"type":"string","enum":["dofollow","nofollow","sponsored"],"default":"dofollow"}}}}}}}}},"responses":{"200":{"description":"Saved links","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"keyword":{"type":"string"},"url":{"type":"string","format":"uri"},"linkType":{"type":"string","enum":["dofollow","nofollow","sponsored"]},"titleAttribute":{"type":["string","null"]},"position":{"type":"integer"}}}}}}}}},"400":{"description":"Invalid ID, or invalid data with Zod details","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object","additionalProperties":true}}}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Content"],"summary":"Delete all in-text links","operationId":"deleteContentLinks","x-required-scope":"content:delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Invalid content ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/publish-async":{"post":{"tags":["Content"],"summary":"Queue publish","description":"Schedules the content for immediate publishing and hands it to the background job. The result shows in the content status. Already published content is updated via `POST /api/content/{id}/publish`.","operationId":"publishContentAsync","x-required-scope":"content:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"Optional target override. `channelId` and `wordPressSiteId` are mutually exclusive.","properties":{"channelId":{"type":["integer","null"],"minimum":1},"wordPressSiteId":{"type":["integer","null"],"minimum":1},"categoryId":{"type":["integer","null"],"minimum":1}}}}}},"responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"queued":{"type":"boolean"},"contentId":{"type":"integer"},"dispatched":{"type":"boolean"}}}}}},"400":{"description":"Invalid ID or target, content already published or in a status that cannot be published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Publishing blocked (customer approval missing) or target not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A remote mutation is in progress, the content changed concurrently or the target is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/reconcile-publish":{"post":{"tags":["Content"],"summary":"Resolve a stuck publish","description":"Checks a content item stuck in `publishing` read-only against WordPress and either finalizes it as published or releases it to draft.","operationId":"reconcileContentPublish","x-required-scope":"content:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Resolved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"outcome":{"type":"string","enum":["released_to_draft","finalized_published"]},"remotePostDeleted":{"type":"boolean"},"externalUrl":{"type":["string","null"]}}}}}},"400":{"description":"Invalid ID or content not locked (`content_not_locked`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Could not be resolved (`reconcile_conflict`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string","enum":["target_invalid","target_unsupported","remote_unverifiable","remote_ambiguous","changed_concurrently","still_running","draft_intent_manual_reconciliation"]}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/request-publication":{"post":{"tags":["Content"],"summary":"Request marketplace publication","description":"Creates a marketplace channel booking from the draft's marketplace target and submits it for publisher review. Idempotent: an existing booking is returned with `alreadyRequested: true` and status 200.","operationId":"requestContentPublication","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Booking already requested","content":{"application/json":{"schema":{"type":"object","properties":{"bookingId":{"type":"integer"},"status":{"type":["string","null"]},"alreadyRequested":{"type":"boolean"},"autoApproved":{"type":"boolean"}}}}}},"201":{"description":"Booking created","content":{"application/json":{"schema":{"type":"object","properties":{"bookingId":{"type":"integer"},"status":{"type":"string","enum":["pending_review","pending_payment","approved"]},"autoApproved":{"type":"boolean"},"alreadyRequested":{"type":"boolean"}}}}}},"400":{"description":"Invalid ID, no marketplace target, listing unavailable or own listing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The publisher does not accept requests from this user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content or marketplace channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Content already published, own target assigned, programme guard (`recurring_program_content_guard`) or concurrent request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/restore-published-url":{"post":{"tags":["Content"],"summary":"Restore published URL","description":"Reads the published post back from WordPress to restore a missing public URL.","operationId":"restoreContentPublishedUrl","x-required-scope":"content:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Restored","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"outcome":{"type":"string","enum":["restored","already_set"]},"url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Invalid ID or not applicable (`restore_not_applicable`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string","enum":["not_found","not_published","no_remote_post"]}}}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Could not be restored (`restore_conflict`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string","enum":["target_invalid","target_unsupported","remote_unverifiable","remote_gone","remote_not_published","url_rejected","changed_concurrently"]}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/{id}/upload-image":{"post":{"tags":["Content"],"summary":"Upload featured image","description":"Uploads a custom featured image (JPEG, PNG, GIF or WebP, at most 10 MB) and sets it on the content.","operationId":"uploadContentImage","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Updated content with the uploaded image","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Content"},{"type":"object","properties":{"mediaId":{"type":"integer"},"s3Url":{"type":"string","format":"uri"}}}]}}}},"400":{"description":"Invalid ID, no file, wrong type or file too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Content not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Content is locked by a running remote mutation or a programme guard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/generate":{"post":{"tags":["Content"],"summary":"Generate free-standing content","description":"Generates AI text for a prompt, or topic suggestions for a website when `websiteUrl` and `websiteName` are sent (then the suggestions branch wins). Not bound to a content item.","operationId":"generateFreeContent","x-required-scope":"content:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","minLength":1},"contentLength":{"type":"string","enum":["short","medium","long","very-long"],"default":"medium"},"language":{"type":"string","enum":["en","de"],"default":"en"},"category":{"type":"string"}}},{"type":"object","required":["websiteUrl","websiteName"],"properties":{"websiteUrl":{"type":"string","format":"uri"},"websiteName":{"type":"string","minLength":1},"categoryName":{"type":"string"},"language":{"type":"string","enum":["en","de"],"default":"en"}}}]}}}},"responses":{"200":{"description":"Generated content or suggestions","content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["content","metadata"],"properties":{"content":{"type":"string"},"title":{"type":"string"},"metadata":{"type":"object","additionalProperties":true,"properties":{"model":{"type":"string"},"tokens":{"type":"integer"},"duration":{"type":"number"}}}}},{"type":"object","required":["keywords"],"properties":{"title":{"type":"string"},"prompt":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}}}}]}}}},"400":{"description":"Invalid request (Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/generate-keywords":{"post":{"tags":["Content"],"summary":"Suggest SEO keywords","description":"AI keyword suggestions for a title and optional HTML content. Billable.","operationId":"generateContentKeywords","x-required-scope":"content:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1},"content":{"type":"string","description":"HTML"},"category":{"type":"string"},"language":{"type":"string","enum":["en","de"],"default":"en"},"websiteName":{"type":"string"}}}}}},"responses":{"200":{"description":"Keywords","content":{"application/json":{"schema":{"type":"object","properties":{"keywords":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":20},"_credits":{"type":"object","properties":{"remaining":{"type":"number"}}}}}}}},"400":{"description":"Invalid request (Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"creditsRequired":{"type":"number"},"creditsAvailable":{"type":"number"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/generate-seo-meta":{"post":{"tags":["Content"],"summary":"Generate SEO meta","description":"AI meta title (at most 60 characters) and meta description (at most 160 characters). Billable.","operationId":"generateContentSeoMeta","x-required-scope":"content:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1},"content":{"type":"string"},"keywords":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"language":{"type":"string","enum":["bg","cs","da","de","el","en","es","et","fi","fr","ga","hr","hu","it","lt","lv","mt","nl","pl","pt","ro","sk","sl","sv"],"default":"de"}}}}}},"responses":{"200":{"description":"Meta title and description","content":{"application/json":{"schema":{"type":"object","properties":{"metaTitle":{"type":"string","maxLength":60},"metaDescription":{"type":"string","maxLength":160}}}}}},"400":{"description":"Invalid request (Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"creditsRequired":{"type":"number"},"creditsAvailable":{"type":"number"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/content/generate-suggestions":{"post":{"tags":["Content"],"summary":"Suggest title, prompt and keywords","description":"AI suggestions for a new article, with a hint when the topic conflicts with existing content. Billable.","operationId":"generateContentSuggestions","x-required-scope":"content:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"websiteUrl":{"type":"string"},"websiteName":{"type":"string"},"categoryName":{"type":"string"},"language":{"type":"string","default":"de"},"title":{"type":"string"},"prompt":{"type":"string"}}}}}},"responses":{"200":{"description":"Suggestions","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"prompt":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"conflict":{"type":"boolean"},"conflictMessage":{"type":"string"}}}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"creditsRequired":{"type":"number"},"creditsAvailable":{"type":"number"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/author-profiles":{"get":{"tags":["Authors"],"summary":"List author profiles","description":"Returns public author identities for the active organization. Requires `content:read`.","operationId":"listAuthorProfiles","x-required-scope":"content:read","responses":{"200":{"description":"Author profiles","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AuthorProfile"}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Authors"],"summary":"Create author profile","description":"Creates an organization-owned public author identity. Requires `content:write`.","operationId":"createAuthorProfile","x-required-scope":"content:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","shortBio"],"properties":{"name":{"type":"string","maxLength":160},"slug":{"type":"string","maxLength":160},"headline":{"type":"string","nullable":true,"maxLength":240},"shortBio":{"type":"string","maxLength":600},"longBio":{"type":"string","nullable":true,"maxLength":4000},"imageUrl":{"type":"string","format":"uri","nullable":true},"socialLinks":{"type":"object","additionalProperties":{"type":"string","format":"uri"}},"credentials":{"type":"array","items":{"type":"object"}},"experience":{"type":"array","items":{"type":"object"}},"publications":{"type":"array","items":{"type":"object"}},"certifications":{"type":"array","items":{"type":"object"}},"template":{"type":"string","enum":["minimal","card"]},"isActive":{"type":"boolean"},"targets":{"type":"array","items":{"type":"object","required":["kind","targetId"],"properties":{"kind":{"type":"string","enum":["wordpress_site","publishing_channel"]},"targetId":{"type":"integer"},"wordpressUserId":{"type":"integer","nullable":true},"isDefault":{"type":"boolean"}}}}}}}}},"responses":{"201":{"description":"Author profile created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorProfile"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/author-profiles/{id}":{"get":{"tags":["Authors"],"summary":"Get author profile","operationId":"getAuthorProfile","x-required-scope":"content:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Author profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorProfile"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Authors"],"summary":"Update author profile","operationId":"updateAuthorProfile","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","shortBio"],"properties":{"name":{"type":"string","maxLength":160},"slug":{"type":"string","maxLength":160},"headline":{"type":"string","nullable":true,"maxLength":240},"shortBio":{"type":"string","maxLength":600},"longBio":{"type":"string","nullable":true,"maxLength":4000},"imageUrl":{"type":"string","format":"uri","nullable":true},"socialLinks":{"type":"object","additionalProperties":{"type":"string","format":"uri"}},"credentials":{"type":"array","items":{"type":"object"}},"experience":{"type":"array","items":{"type":"object"}},"publications":{"type":"array","items":{"type":"object"}},"certifications":{"type":"array","items":{"type":"object"}},"template":{"type":"string","enum":["minimal","card"]},"isActive":{"type":"boolean"},"targets":{"type":"array","items":{"type":"object","required":["kind","targetId"],"properties":{"kind":{"type":"string","enum":["wordpress_site","publishing_channel"]},"targetId":{"type":"integer"},"wordpressUserId":{"type":"integer","nullable":true},"isDefault":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"Author profile updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorProfile"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Authors"],"summary":"Archive author profile","operationId":"archiveAuthorProfile","x-required-scope":"content:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Author profile archived"},"409":{"description":"Profile is still assigned to content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/wordpress-sites":{"get":{"tags":["WordPress Sites"],"summary":"List WordPress sites","description":"Returns all connected WordPress sites for the authenticated user or organization.","operationId":"listWordPressSites","x-required-scope":"sites:read","parameters":[{"name":"filter","in":"query","schema":{"type":"string","enum":["mine","all","deleted","tenant"]},"description":"Filter mode. 'all' and 'deleted' require admin. 'tenant' returns only sites of the active tenant (the organization of the session or key, otherwise own sites without organization)."}],"responses":{"200":{"description":"List of WordPress sites","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordPressSiteListItem"}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["WordPress Sites"],"summary":"Connect WordPress site","description":"Connects a WordPress site using an Application Password. Format for apiToken: `username:applicationpassword`.","operationId":"createWordPressSite","x-required-scope":"sites:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","name","apiToken"],"properties":{"url":{"type":"string","format":"uri","description":"WordPress site URL"},"name":{"type":"string"},"apiToken":{"type":"string","writeOnly":true,"description":"Write-only credential in the format username:applicationpassword"},"status":{"type":"string"},"description":{"type":"string"},"editorialLine":{"type":"string"},"excludedTopics":{"type":"string"},"allowedLanguages":{"type":"array","items":{"type":"string"}},"primaryLanguage":{"type":"string"}}}}}},"responses":{"201":{"description":"Site connected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordPressSite"}}}},"403":{"description":"Site limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Domain already connected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/wordpress-sites/{id}":{"get":{"tags":["WordPress Sites"],"summary":"Get WordPress site","description":"Site details incl. `maxPostsPerDay`; the API token stays redacted. With an API key only sites of the key's tenant (no admin cross-tenant access, no deleted sites); foreign, missing and malformed ids return 404 `not_found`.","operationId":"getWordPressSite","x-required-scope":"sites:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Site details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordPressSite"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["WordPress Sites"],"summary":"Update WordPress site","description":"With an API key (`sites:write` plus RBAC `wordpress.sites.edit`) the body may ONLY contain `maxPostsPerDay` (positive integer or null = unlimited); any other field gives 400 `unknown_field`. Programme-bound sites give 409 `program_managed`; a site that is today's target of an active one-shot draft gives 409 `site_target_locked`. The dashboard session may change all fields listed below.","operationId":"updateWordPressSite","x-required-scope":"sites:write","x-required-permission":"wordpress.sites.edit","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"maxPostsPerDay":{"type":["integer","null"],"minimum":1,"description":"Daily publication limit of the site (Europe/Vienna calendar day); null = unlimited. The only field allowed with an API key."},"name":{"type":"string"},"url":{"type":"string","format":"uri","description":"Must use HTTP or HTTPS. A scheme/host/port change requires a different non-empty apiToken in the same request."},"apiToken":{"type":"string","writeOnly":true,"description":"Optional credential rotation. A URL-origin change requires a non-empty credential whose effective Authorization header differs from the stored credential."},"description":{"type":"string"},"editorialLine":{"type":"string"},"excludedTopics":{"type":"string"},"allowedLanguages":{"type":"array","items":{"type":"string"}},"primaryLanguage":{"type":"string"},"allowMismatchedLanguages":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Site updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordPressSite"}}}},"400":{"description":"Invalid update (`unknown_field`, `invalid_input`) or origin change without a new API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not authorized (`forbidden_scope`, missing `wordpress.sites.edit`, `org_mismatch`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`program_managed`, `site_target_locked` or a concurrent change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["WordPress Sites"],"summary":"Delete WordPress site","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Soft-deletes the site. The site can be restored by an admin.","operationId":"deleteWordPressSite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Site deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"isOwnSite":{"type":"boolean"},"siteName":{"type":"string"}}}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/wordpress-sites/daily-capacity":{"get":{"tags":["WordPress Sites"],"summary":"Get today's daily capacity of a site","description":"Read-only, reserves nothing. With an API key: today's occupancy (Europe/Vienna calendar day, WordPress posts plus CMM reservations) of one site of the key's tenant, addressed by `siteId` (required); deleted, external and foreign sites give 404 `not_found`. The dashboard session may also pass a `channelId` and receives only a localized `{ warning }`.","operationId":"getWordPressSiteDailyCapacity","x-required-scope":"sites:read","x-required-permission":"ownership","parameters":[{"name":"siteId","in":"query","required":false,"description":"Required with an API key","schema":{"type":"integer","minimum":1,"maximum":2147483647}},{"name":"channelId","in":"query","required":false,"description":"Session only (dashboard)","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Daily capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDailyCapacity"}}}},"400":{"description":"Missing or invalid `siteId` (`invalid_input`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not a site of the key's tenant (`not_found`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`capacity_unavailable` (WordPress or the database could not be read) or `org_resolution_failed` (the key's organization could not be resolved)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/wordpress-sites/auto-connect":{"post":{"tags":["WordPress Sites"],"summary":"Auto-connect WordPress site","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Automatically creates an Application Password via the WordPress REST API. The admin password is used once and never stored.","operationId":"autoConnectWordPressSite","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","username","password"],"properties":{"url":{"type":"string","format":"uri","description":"WordPress site URL"},"username":{"type":"string","description":"WordPress admin username"},"password":{"type":"string","description":"WordPress admin password (not stored)"},"name":{"type":"string","description":"Display name for the site"}}}}}},"responses":{"200":{"description":"Site connected","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"site":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"}}},"message":{"type":"string"}}}}}},"400":{"description":"Connection failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Domain already connected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/wordpress-sites/{id}/categories":{"get":{"tags":["WordPress Sites"],"summary":"List site categories","description":"Stored categories of the site. With `sync=true` they are first synced from WordPress; that needs site access and the `sites:write` scope (plain reads need `sites:read`; marketplace listings are readable without site access). If the sync fails but categories are stored, those are returned with 200.","operationId":"listWordPressSiteCategories","x-required-scope":"sites:read","x-required-scope-by-parameter":{"sync=true":"sites:write"},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"sync","in":"query","schema":{"type":"string","enum":["true"]},"description":"`true` syncs from WordPress first (scope `sites:write`)"}],"responses":{"200":{"description":"Categories","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"wordPressSiteId":{"type":["integer","null"]},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"wordPressCategoryId":{"type":["integer","null"]},"status":{"type":"string","enum":["local","synced","error"]},"parentId":{"type":["integer","null"],"description":"After a sync: the WordPress parent id"},"metadata":{"type":"object","additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}}}}}}},"400":{"description":"Invalid site id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"No access to the site (or sync without site access)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Site not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Sync failed and no categories are stored","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["wordpress_categories_sync_failed"]},"wordpressStatus":{"type":"integer","description":"HTTP status from WordPress, 0 = network error"},"detail":{"type":"string"},"hint":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["WordPress Sites"],"summary":"Create local category","description":"Creates a category record for the site in ChatCMM only; it is not pushed to WordPress. Requires the site edit permission.","operationId":"createWordPressSiteCategory","x-required-scope":"sites:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","slug"],"properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1},"description":{"type":"string"},"wordPressCategoryId":{"type":"number"},"status":{"type":"string","enum":["local","synced","error"],"default":"local"},"parentId":{"type":["number","null"]},"metadata":{"type":"object","additionalProperties":true,"default":{}}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"wordPressSiteId":{"type":["integer","null"]},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"wordPressCategoryId":{"type":["integer","null"]},"status":{"type":"string","enum":["local","synced","error"]},"parentId":{"type":["integer","null"],"description":"After a sync: the WordPress parent id"},"metadata":{"type":"object","additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}}}}}},"400":{"description":"Invalid site id or request data (`details`: serialized validation error, not a stable contract)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object","additionalProperties":true}}}}}},"403":{"description":"Missing site edit permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Site not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/wordpress-sites/suggest":{"post":{"tags":["WordPress Sites"],"summary":"Suggest sites for an article","description":"Up to 5 accessible sites with a selectable publishing channel, ranked for the article (keyword prefilter, then AI ranking). Billable by token usage after a successful AI ranking; if the AI ranking fails, a keyword-only result is returned without charge.","operationId":"suggestWordPressSites","x-required-scope":"sites:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"At least 3 words"},"keywords":{"type":"array","items":{"type":"string"},"default":[]},"prompt":{"type":"string","default":""},"language":{"type":"string"}}}}}},"responses":{"200":{"description":"Suggestions, best first","content":{"application/json":{"schema":{"type":"object","properties":{"suggestions":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"wordPressSiteId":{"type":"integer"},"siteName":{"type":"string"},"siteUrl":{"type":"string"},"primaryLanguage":{"type":"string"},"fitScore":{"type":"number","minimum":0,"maximum":100},"reasoning":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}}}}}}}}}},"400":{"description":"Title missing or shorter than 3 words","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"creditsRequired":{"type":"number"},"creditsAvailable":{"type":"number"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/site-groups":{"get":{"tags":["WordPress Sites"],"summary":"List site groups","description":"Lists the caller's site groups with their member sites. Requires the RBAC permission `wordpress.sites.view`. API keys receive the allow-listed `SiteGroup` view; only sites of the key's tenant are listed as members. With an API key the group must belong to the key's organization (without an organization: to the key owner, `orgId IS NULL`). Foreign, missing and malformed ids all return an identical 404 `not_found` (no existence oracle). The dashboard session keeps its own, wider ownership rule and its unchanged response shape.","operationId":"listSiteGroups","x-required-scope":"sites:read","x-required-permission":"wordpress.sites.view","responses":{"200":{"description":"Site groups, sorted by name","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SiteGroup"}}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["WordPress Sites"],"summary":"Create site group","description":"Creates a group, optionally with member sites. Requires `wordpress.sites.create`. Every `siteIds` entry must be a non-deleted site of the key's tenant, otherwise 400 `site_not_owned` and nothing is created. The group can then be used as `channelFilter.groupId` (with `mode = \"group\"`) in `PATCH /api/campaigns/{id}/recurring`; that endpoint checks the group and its member sites with the same strict tenant rule (#1168). With an API key the group must belong to the key's organization (without an organization: to the key owner, `orgId IS NULL`). Foreign, missing and malformed ids all return an identical 404 `not_found` (no existence oracle). The dashboard session keeps its own, wider ownership rule and its unchanged response shape.","operationId":"createSiteGroup","x-required-scope":"sites:write","x-required-permission":"wordpress.sites.create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteGroupCreate"}}}},"responses":{"201":{"description":"Created group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteGroup"}}}},"400":{"description":"`invalid_input`, `unknown_field`, `site_not_owned`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/site-groups/{id}":{"patch":{"tags":["WordPress Sites"],"summary":"Update site group","description":"Renames the group, changes its description and/or replaces its members (`siteIds`, atomic). Replaces the members visible to the key; members outside the key's tenant stay unchanged. Requires `wordpress.sites.edit`. With an API key the group must belong to the key's organization (without an organization: to the key owner, `orgId IS NULL`). Foreign, missing and malformed ids all return an identical 404 `not_found` (no existence oracle). The dashboard session keeps its own, wider ownership rule and its unchanged response shape.","operationId":"updateSiteGroup","x-required-scope":"sites:write","x-required-permission":"wordpress.sites.edit","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteGroupUpdate"}}}},"responses":{"200":{"description":"Updated group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteGroup"}}}},"400":{"description":"`invalid_input`, `unknown_field`, `site_not_owned`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["WordPress Sites"],"summary":"Delete site group","description":"Deletes the group; its memberships are removed with it (the sites stay). Requires `wordpress.sites.delete`. A recurring campaign that still points at the group via `channelFilter.groupId` is not changed here and fails its next check until a new group is set. With an API key the group must belong to the key's organization (without an organization: to the key owner, `orgId IS NULL`). Foreign, missing and malformed ids all return an identical 404 `not_found` (no existence oracle). The dashboard session keeps its own, wider ownership rule and its unchanged response shape.","operationId":"deleteSiteGroup","x-required-scope":"sites:write","x-required-permission":"wordpress.sites.delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","enum":[true]}}}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/models":{"get":{"tags":["Campaigns"],"summary":"List AI models","description":"Lists the text and image models of the AI Gateway catalog (public catalog data, no tenant data). Use `gatewayId` as `channelFilter.generationModels.text` / `.image` in `PATCH /api/campaigns/{id}/recurring` (a `text` model for `text`, an `image` model for `image`). With an API key `type` and `grouped` are validated strictly (400 `invalid_input`) and an unavailable or empty catalog answers 503 `model_catalog_unavailable`. The dashboard session receives the legacy response without `gatewayId` and keeps its lenient query parsing.","operationId":"listAiModels","x-required-scope":"campaigns:read","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["text","image","all"],"default":"all"}},{"name":"grouped","in":"query","schema":{"type":"string","enum":["true","false"],"default":"false"},"description":"`true` returns `models` as `{ openai, anthropic, google }` instead of a flat array."}],"responses":{"200":{"description":"Model catalog","content":{"application/json":{"schema":{"type":"object","required":["models","openaiAvailable","anthropicAvailable","googleAvailable","total"],"properties":{"models":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/AiModel"}},{"type":"object","required":["openai","anthropic","google"],"properties":{"openai":{"type":"array","items":{"$ref":"#/components/schemas/AiModel"}},"anthropic":{"type":"array","items":{"$ref":"#/components/schemas/AiModel"}},"google":{"type":"array","items":{"$ref":"#/components/schemas/AiModel"}}}}]},"openaiAvailable":{"type":"boolean"},"anthropicAvailable":{"type":"boolean"},"googleAvailable":{"type":"boolean"},"total":{"type":"integer"}}}}}},"400":{"description":"`invalid_input` (`type` or `grouped` invalid).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`model_catalog_unavailable` (AI model catalog unavailable) or `org_resolution_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","description":"Returns campaigns with article counts and cost summaries. API keys receive the allow-listed view (`PublicCampaignListItem` / `PublicCampaignArticle`): no approval tokens, agent log or conversation, and `channelFilter` only with its editable fields. The dashboard session receives an undocumented superset.","operationId":"listCampaigns","x-required-scope":"campaigns:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":25}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicCampaignListItem"}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Campaigns"],"summary":"Create campaign","description":"Creates a new campaign with articles. Each article maps to a channel or own WordPress site.","operationId":"createCampaign","x-required-scope":"campaigns:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","customer","topic","articles"],"properties":{"name":{"type":"string"},"customer":{"type":"string"},"customerOrganizationId":{"type":"integer"},"topic":{"type":"string"},"targetAudience":{"type":"string"},"totalBudget":{"type":"number"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"language":{"type":"string","default":"de"},"summary":{"type":"string"},"campaignLinks":{"type":"array","maxItems":10,"description":"At most 10 links, http(s) only, no duplicate URL or anchor text (400 `invalid_input` otherwise). Same limits as `PUT /api/campaigns/{id}/links`, but lenient input: a URL without protocol gets `https://` and unknown link fields are ignored (`PUT .../links` is strict: no default protocol, unknown fields are 400 `unknown_field`).","items":{"$ref":"#/components/schemas/CampaignLink"}},"includeOwnSites":{"type":"boolean"},"includeMarketplace":{"type":"boolean"},"articles":{"type":"array","items":{"type":"object","required":["siteName","title","topic"],"properties":{"channelId":{"type":"integer"},"wordPressSiteId":{"type":"integer"},"wordPressCategoryId":{"type":"integer"},"isOwnSite":{"type":"boolean"},"siteName":{"type":"string"},"title":{"type":"string"},"topic":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"scheduledDate":{"type":"string","format":"date"},"estimatedCost":{"type":"integer","description":"Cent (EUR), #1160; bei Marketplace-Zielen gilt der Listing-Preis"}}}}}}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"description":"Validation error. Stable `code` values: `invalid_target`, `own_site_mismatch`, `category_not_on_site`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Site or Marketplace channel not owned by or not bookable for the caller (`code`: `site_not_accessible`, `channel_not_accessible`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}":{"get":{"tags":["Campaigns"],"summary":"Get campaign with articles","description":"API keys receive the allow-listed view: `channelFilter` only with its editable fields, and approval tokens, agent log and server-owned fields are never returned. The dashboard session receives an undocumented superset; with a session and an API key at the same time, the session applies. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"getCampaign","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Campaign with articles","content":{"application/json":{"schema":{"type":"object","required":["campaign","articles"],"properties":{"campaign":{"$ref":"#/components/schemas/PublicCampaign"},"articles":{"type":"array","items":{"$ref":"#/components/schemas/PublicCampaignArticle"}}}}}}},"403":{"description":"Missing key scope or organization mismatch (`forbidden_scope`, `org_mismatch`, `org_membership_revoked`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Campaigns"],"summary":"Update campaign","description":"Updates the plain campaign fields. Recurring settings are changed only via `/api/campaigns/{id}/recurring`, `/recurring/activate` and `/recurring/pause` (an API key sending recurring keys gets 400 `unknown_field`; the dashboard session additionally accepts the `RecurringConfigPatch` keys plus `isRecurring`). `status` is not settable (booking workflow); `summary` and `campaignLinks` are not settable via PATCH. Setting `customerOrganizationId`, `primaryContactUserId`, `campaignApprovalMode` or `campaignApprovalMinCount` can lift the customer approval block and therefore additionally requires the key scope `content:publish` and the RBAC permission `content.publish`. An API key has no admin bypass for the customer organization (`customer_org_not_owned`). Campaigns managed by an automation program return 409 `program_managed`. The response is the flat `PublicCampaign` view. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"updateCampaign","x-required-scope":"campaigns:write","x-conditional-additional-scopes":{"content:publish":["customerOrganizationId","primaryContactUserId","campaignApprovalMode","campaignApprovalMinCount"]},"x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdate"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCampaign"}}}},"400":{"description":"`invalid_input`, `unknown_field`, `primary_contact_requires_organization`, `primary_contact_not_member`, `primary_contact_not_accepted`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope` (content:publish missing), `forbidden_permission` (RBAC content.publish), `customer_org_not_owned`, `org_mismatch`, `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`program_managed`, `one_shot_active`, `conflict_stale` (stale `expectedUpdatedAt`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Campaigns"],"summary":"Delete campaign","description":"Moves the campaign to the trash (soft delete). A second DELETE on a trashed campaign returns 404. Permanent deletion is possible only for admins in the browser session. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"deleteCampaign","x-required-scope":"campaigns:write","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}},{"name":"permanent","in":"query","schema":{"type":"boolean"},"description":"Permanently delete a trashed campaign. Admin browser session only; an API key or a non-admin session gets 403 `forbidden_permission`."}],"responses":{"200":{"description":"Campaign deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"action":{"type":"string","enum":["moved_to_trash","permanently_deleted"]}}}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission` (permanent=true), `org_mismatch`, `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`program_managed`, `one_shot_active`, `conflict_stale`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/links":{"get":{"tags":["Campaigns"],"summary":"Get campaign-wide links","description":"Returns the campaign-wide keyword links (the fallback for every generated article without its own article links). Legacy data with more than `maxLinks` entries is returned unchanged. An unknown stored `linkType` is returned as `dofollow`. `updatedAt` also changes when a recurring run claims a topic or remembers one. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"getCampaignLinks","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Campaign links","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignLinks"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"put":{"tags":["Campaigns"],"summary":"Replace campaign-wide links","description":"Replaces the whole list (an empty list removes the links). At most 10 links, `http`/`https` only without credentials, no duplicate URL (compared as `URL.href`) and no duplicate anchor text (case-insensitive); a violation returns 400 `invalid_input` with `details.reason = duplicate_link` and the `index`. `linkType` is a hint stored with the link; today only the mock generation path renders it as `rel`. Unknown fields return 400 `unknown_field`. `expectedUpdatedAt` is optional: after every recurring run the token is stale (409 `conflict_stale`), so re-read first. For a RECURRING campaign (`isRecurring` or a recurrence pattern set) the links flow into every automatically generated article, so an API key additionally needs the scope `content:publish` (otherwise 403 `forbidden_scope`) and the RBAC permission `content.publish` (403 `forbidden_permission`). Campaigns managed by an automation program return 409 `program_managed`; an active one-shot draft returns 409 `one_shot_active`. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"updateCampaignLinks","x-required-scope":"campaigns:write","x-conditional-additional-scopes":{"content:publish":["recurring campaigns"]},"x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignLinksUpdate"}}}},"responses":{"200":{"description":"Links stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignLinks"}}}},"400":{"description":"`invalid_input` (also more than 10 links, `duplicate_link`), `unknown_field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope` (content:publish, recurring campaigns only), `forbidden_permission` (content.publish, recurring campaigns only), `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`conflict_stale` (also a concurrent change), `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/roadmap":{"get":{"tags":["Campaigns"],"summary":"List open roadmap topics","description":"Returns the open roadmap topics of a recurring campaign (`status = planned`, no content, no booking) in the order the generator draws them (scheduled date, series order, id), at most 500; `truncated` is true when more exist. `eligible` and `blockedReason` mirror the worker candidate filter exactly (no normalisation): `locked`, `content_in_progress`, `already_generated_topic`, `missing_category` (daily pilot and draft upload need a category per topic) and `site_fit` (only with exactly one fixed target site: language mismatch, excluded topic or editorial site, see `siteFitReason`). The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"listRoadmapTopics","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Open roadmap topics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapTopics"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Campaigns"],"summary":"Add roadmap topics","description":"Adds 1 to 52 topics to a recurring campaign without AI and without credits. New topics are unlocked, so the generator draws them from the next cycle on. Duplicates (case-insensitive, whitespace and Unicode normalised) against open and generated topics, or within the request, are returned in `skipped`; cancelled topics do not block. At most 365 open topics per campaign (400 `invalid_input`, `details.reason = roadmap_full`, nothing written). A category needs exactly one fixed target site (`channelFilter.mode = list` with one site) and must belong to that site and be synced to WordPress (400 `category_invalid`, `details.reason` one of `no_single_target_site`, `category_not_found`, `category_not_synced`, `site_not_in_tenant`, `site_unavailable`, `category_required`); a foreign and a non-existent category are indistinguishable. The call is rejected with 409 `roadmap_cycle_in_progress` while a cycle is due (within 30 minutes after the planned time) or running (latest claim younger than 30 minutes without a booking), so a new topic never skips the due cycle. A non-recurring campaign returns 400 (`details.reason = campaign_not_recurring`). Requires the `content.publish` permission and, with an API key, BOTH scopes `campaigns:write` and `content:publish`. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"addRoadmapTopics","x-required-scope":"campaigns:write","x-required-additional-scopes":["content:publish"],"x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapAdd"}}}},"responses":{"200":{"description":"Nothing created (all topics were duplicates)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapAddResult"}}}},"201":{"description":"At least one topic created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapAddResult"}}}},"400":{"description":"`invalid_input` (also `roadmap_full`, `campaign_not_recurring`), `unknown_field`, `category_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission` (content.publish), `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`roadmap_cycle_in_progress`, `conflict_stale` (also a lock timeout), `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/roadmap/{articleId}":{"patch":{"tags":["Campaigns"],"summary":"Change a roadmap topic","description":"Changes `topic`, `keywords`, `scheduledDate` (ordering hint only: the generator overwrites it on booking) or `wordPressCategoryId` of a planned topic. At least one field; `expectedUpdatedAt` is optional. Only topics without a site or channel can be changed (409 `roadmap_topic_not_planned`, `details.reason = site_bound`; edit those in the dashboard). A topic that is booked, generated, generating or reserved for a cycle returns 409 `roadmap_topic_not_planned` (`not_planned`, `content_in_progress`, `reserved_for_cycle`). A title that already exists returns 400 `invalid_input` with `details.reason = duplicate_topic`. The category rules and the cycle guard of `POST` apply. Requires the `content.publish` permission and, with an API key, BOTH scopes `campaigns:write` and `content:publish`. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"updateRoadmapTopic","x-required-scope":"campaigns:write","x-required-additional-scopes":["content:publish"],"x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}},{"name":"articleId","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapPatch"}}}},"responses":{"200":{"description":"Updated topic","content":{"application/json":{"schema":{"type":"object","required":["topic"],"properties":{"topic":{"$ref":"#/components/schemas/RoadmapTopic"}}}}}},"400":{"description":"`invalid_input` (also `duplicate_topic`, `campaign_not_recurring`), `unknown_field`, `category_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission` (content.publish), `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign or topic not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`roadmap_topic_not_planned`, `roadmap_cycle_in_progress`, `conflict_stale`, `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Campaigns"],"summary":"Cancel a roadmap topic","description":"Soft delete: sets the topic to `cancelled`. A cancelled topic is no longer drawn, does not use site capacity and does not block a new topic with the same title. Cancelling an already cancelled topic is idempotent (`changed: false`). Cancelling the last candidate with a category of a daily pilot pauses the campaign at its next run (intended). The request must have no body. Requires the `content.publish` permission. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"cancelRoadmapTopic","x-required-scope":"campaigns:write","x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}},{"name":"articleId","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Cancelled topic","content":{"application/json":{"schema":{"type":"object","required":["topic","changed"],"properties":{"topic":{"$ref":"#/components/schemas/RoadmapTopic"},"changed":{"type":"boolean"}}}}}},"400":{"description":"`invalid_input` (non-empty body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission` (content.publish), `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign or topic not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`roadmap_topic_not_planned`, `roadmap_cycle_in_progress`, `conflict_stale`, `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/recurring":{"get":{"tags":["Campaigns"],"summary":"Get recurring configuration","description":"Returns the editable recurring configuration as an allow-listed view. The stored `channelFilter` is never returned raw: server-owned fields are not readable. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"getRecurringConfig","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Recurring configuration","content":{"application/json":{"schema":{"type":"object","required":["recurring"],"properties":{"recurring":{"$ref":"#/components/schemas/RecurringConfig"}}}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Campaigns"],"summary":"Update recurring configuration","description":"Partial update of the recurring configuration. State and configuration are separate: `isRecurring` is NOT accepted (use `/recurring/activate` and `/recurring/pause`). Unknown fields are rejected with 400 `unknown_field`. `channelFilter` is deep-merged over the stored value; `null` clears `publicationMode`, `generationModels` and `contentLength`. `expectedUpdatedAt` is optional; if it differs from the stored `updatedAt` the call returns 409 `conflict_stale`. `updatedAt` has second granularity: a change made by the dashboard in the same second as your read is not detected. `nextGenerationAt` is recomputed only on a real schedule change (pattern, day, time, pauseOnHolidays, effective time zone); sending identical values changes nothing. Without `channelFilter.timeZone` the server time zone (UTC) applies. Requires the `content.publish` permission. With an API key the key needs BOTH scopes `campaigns:write` and `content:publish` (otherwise 403 `forbidden_scope` and nothing is written), because this call can enable automatic WordPress publishing. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"updateRecurringConfig","x-required-scope":"campaigns:write","x-required-additional-scopes":["content:publish"],"x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringConfigPatch"}}}},"responses":{"200":{"description":"Updated configuration","content":{"application/json":{"schema":{"type":"object","required":["recurring","changed","scheduleChanged"],"properties":{"recurring":{"$ref":"#/components/schemas/RecurringConfig"},"changed":{"type":"boolean","description":"Whether anything was written."},"scheduleChanged":{"type":"boolean","description":"Whether the schedule (not only other settings) changed."}}}}}},"400":{"description":"Validation error. `code`: `invalid_input`, `unknown_field`, `guarded_pilot_invalid`, `site_not_owned`, `group_empty`, `model_unavailable`, `draft_upload_target_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission` (content.publish), `group_not_owned`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`conflict_stale` (stale `expectedUpdatedAt`, concurrent write, or lock timeout: retry), `one_shot_active`, `program_managed` (campaign is managed by the editorial program) or `roadmap_category_conflict` (a `channelFilter` change would leave open roadmap topics with a category of another site; adjust or cancel those topics first, nothing is written).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`model_catalog_unavailable` (AI model catalog unavailable) or `org_resolution_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/campaigns/{id}/recurring/activate":{"post":{"tags":["Campaigns"],"summary":"Activate recurring","description":"Activates (or resumes) the recurring automation. Idempotent: an already active campaign with a scheduled run is left unchanged (`changed: false`). The next run is computed once on activation (next occurrence after now). Requires a configured `recurrencePattern`. With an API key the key needs BOTH scopes `campaigns:write` and `content:publish` (otherwise 403 `forbidden_scope` and nothing is written), because activation starts automatic WordPress publishing. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"activateRecurring","x-required-scope":"campaigns:write","x-required-additional-scopes":["content:publish"],"x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringStateChange"}}}},"responses":{"200":{"description":"Resulting configuration","content":{"application/json":{"schema":{"type":"object","required":["recurring","changed"],"properties":{"recurring":{"$ref":"#/components/schemas/RecurringConfig"},"changed":{"type":"boolean"}}}}}},"400":{"description":"`invalid_input` (no schedule configured, the campaign is `cancelled` (`details.field: status`), or the local time does not exist on a DST change), `unknown_field`, `guarded_pilot_invalid`, `draft_upload_target_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`conflict_stale`, `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/recurring/pause":{"post":{"tags":["Campaigns"],"summary":"Pause recurring","description":"Pauses the recurring automation and clears the next run. Idempotent: an already paused campaign is left unchanged. Pause always wins; there is no compare-and-set on the schedule. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"pauseRecurring","x-required-scope":"campaigns:write","x-required-permission":"content.publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringStateChange"}}}},"responses":{"200":{"description":"Resulting configuration","content":{"application/json":{"schema":{"type":"object","required":["recurring","changed"],"properties":{"recurring":{"$ref":"#/components/schemas/RecurringConfig"},"changed":{"type":"boolean"}}}}}},"400":{"description":"`invalid_input` or `unknown_field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `forbidden_permission`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`conflict_stale`, `one_shot_active` or `program_managed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/recurring/status":{"get":{"tags":["Campaigns"],"summary":"Get recurring status","description":"Derived status: next run (unix, ISO, time zone), counters, last successful run, pause reason (from the owner notification, never its text), pilot-guard result and costs. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"getRecurringStatus","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}}],"responses":{"200":{"description":"Recurring status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringStatus"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/campaigns/{id}/recurring/runs":{"get":{"tags":["Campaigns"],"summary":"List recurring runs","description":"Run history: one entry per claimed cycle of the recurring generator, newest cycle first. Programme campaigns (editorial and product programmes) are not recorded. The campaign must belong to the caller's organization (without an organization: to the caller). Foreign, missing and deleted campaigns all return an identical 404 `not_found` (no existence oracle).","operationId":"listRecurringRuns","x-required-scope":"campaigns:read","x-required-permission":"ownership","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":2147483647}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"before","in":"query","required":false,"description":"Cursor: only runs with `cycleAt` below this value (use `nextBefore` of the previous page).","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Run history page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringRunsPage"}}}},"400":{"description":"`invalid_input` (limit or cursor).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`forbidden_scope`, `org_mismatch` or `org_membership_revoked`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Campaign not found (`code`: `not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/channels":{"get":{"tags":["Channels"],"summary":"List marketplace channels","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Returns available channel listings from the marketplace. Channels represent connected WordPress sites or manually fulfilled external publisher offers.","operationId":"listChannels","parameters":[{"name":"view","in":"query","schema":{"type":"string","enum":["marketplace"]},"description":"Advertiser catalogue for every role: only available listings on existing sites; no publisher/admin visibility bypass or purchase prices."},{"name":"available","in":"query","schema":{"type":"boolean"},"description":"Filter by availability"},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"linkType","in":"query","schema":{"type":"string","enum":["dofollow","nofollow","mixed","sponsored"]}},{"name":"sponsored","in":"query","schema":{"type":"boolean"}},{"name":"minPrice","in":"query","schema":{"type":"number","minimum":0}},{"name":"maxPrice","in":"query","schema":{"type":"number","minimum":0}},{"name":"sort","in":"query","schema":{"type":"string","enum":["newest","priceAsc","priceDesc","sistrixDesc"]}},{"name":"categoryId","in":"query","schema":{"type":"integer"}},{"name":"siteId","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated list of channels","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ChannelListing"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}}}},"post":{"tags":["Channels"],"summary":"Create or update channel listing","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Lists a WordPress site on the marketplace or updates an existing listing.","operationId":"createChannel","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["wordPressSiteId","pricePerBooking"],"properties":{"wordPressSiteId":{"type":"integer"},"catalog":{"$ref":"#/components/schemas/ChannelCatalog"},"pricePerBooking":{"type":"string","description":"Price in EUR (e.g. '49.90')"},"isAvailable":{"type":"boolean","default":false},"description":{"type":"string"},"categoryId":{"type":"integer"},"editorialLine":{"type":"string"},"excludedTopics":{"type":"string"},"monthlyVisitors":{"type":"integer"},"linkType":{"type":"string","enum":["dofollow","nofollow","mixed","sponsored"]},"bookingDurationDays":{"type":"integer"},"sponsoredLabelRequired":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Channel updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelListing"}}}},"201":{"description":"Channel created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelListing"}}}}}}},"/api/channels/{id}":{"get":{"tags":["Channels"],"summary":"Get channel details","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"getChannel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Channel details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelListing"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Channels"],"summary":"Update channel listing","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"updateChannel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"catalog":{"$ref":"#/components/schemas/ChannelCatalog"},"pricePerBooking":{"type":"string"},"isAvailable":{"type":"boolean"},"description":{"type":"string"},"categoryId":{"type":"integer"},"editorialLine":{"type":"string"},"excludedTopics":{"type":"string"},"monthlyVisitors":{"type":"integer"},"linkType":{"type":"string","enum":["dofollow","nofollow","mixed","sponsored"]},"bookingDurationDays":{"type":"integer"},"sponsoredLabelRequired":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Channel updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelListing"}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Channels"],"summary":"Delete channel listing","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"deleteChannel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Channel deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"action":{"type":"string"},"siteName":{"type":"string"}}}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/channels/{id}/supplier":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"security":[{"SessionCookie":[]}],"x-auth":"session","tags":["Channels"],"operationId":"getChannelSupplier","summary":"Get private supplier details","description":"Offering owner or active offering-account owner/admin only; site ownership also required. Cache-Control: private, no-store.","responses":{"200":{"description":"Supplier details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelSupplier"}}}},"404":{"description":"Missing listing or no supplier access"}}},"put":{"security":[{"SessionCookie":[]}],"x-auth":"session","tags":["Channels"],"operationId":"updateChannelSupplier","summary":"Replace private supplier details","description":"Same offering-account guard as GET. Original import metadata is preserved.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelSupplier"}}}},"responses":{"200":{"description":"Saved supplier details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelSupplier"}}}},"400":{"description":"Invalid supplier fields"},"404":{"description":"Missing listing or no supplier access"}}}},"/api/bookings":{"get":{"tags":["Bookings"],"summary":"List bookings","description":"Returns marketplace bookings. Filter by role to see bookings as creator (advertiser) or publisher.","operationId":"listBookings","x-required-scope":"bookings:read","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","accepted","rejected","in_progress","content_submitted","revision_requested","published","completed","cancelled"]}},{"name":"role","in":"query","schema":{"type":"string","enum":["creator","publisher"]},"description":"View as advertiser or publisher"},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated list of bookings","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Bookings"],"summary":"Create booking","description":"Books a marketplace channel for a sponsored post.","operationId":"createBooking","x-required-scope":"bookings:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channelListingId","contentTitle"],"properties":{"channelListingId":{"type":"integer"},"contentTitle":{"type":"string"},"scheduledDate":{"type":"integer","description":"Unix timestamp"},"notes":{"type":"string"}}}}}},"responses":{"201":{"description":"Booking created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Channel not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Channel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/bookings/{id}":{"get":{"tags":["Bookings"],"summary":"Get booking details","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"getBooking","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Booking details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Bookings"],"summary":"Update booking","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Update booking status, schedule, or publish URL.","operationId":"updateBooking","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","accepted","rejected","in_progress","content_submitted","revision_requested","published","completed","cancelled"]},"scheduledDate":{"type":"integer","description":"Unix timestamp"},"notes":{"type":"string"},"publishedUrl":{"type":"string","format":"uri"},"paymentMethod":{"type":"string","enum":["stripe","invoice","wallet"]}}}}}},"responses":{"200":{"description":"Booking updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/media":{"get":{"tags":["Media"],"summary":"List media items","description":"Returns uploaded and AI-generated images.","operationId":"listMedia","x-required-scope":"media:read","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Search by filename or title"},{"name":"source","in":"query","schema":{"type":"string","enum":["upload","ai","wordpress"]}},{"name":"mimeType","in":"query","schema":{"type":"string"},"description":"Filter by MIME type (e.g. image/png)"},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated list of media items","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MediaItem"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Media"],"summary":"Create media item","description":"Uploads an image (multipart, stored in the media bucket, variants processed in the background) or registers an image that already lives at an external URL (JSON).","operationId":"createMedia","x-required-scope":"media:write","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"JPEG, PNG, GIF, WebP or SVG"},"altText":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"tags":{"type":"string","description":"Comma-separated"}}}},"application/json":{"schema":{"type":"object","required":["filename","originalName","mimeType","url","size"],"properties":{"filename":{"type":"string"},"originalName":{"type":"string"},"mimeType":{"type":"string"},"url":{"type":"string"},"size":{"type":"number","description":"Bytes"},"width":{"type":"number"},"height":{"type":"number"},"thumbnailUrl":{"type":"string"},"altText":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":"string","enum":["upload","ai","wordpress"],"default":"upload"},"aiModel":{"type":"string"},"aiPrompt":{"type":"string"}}}}}},"responses":{"201":{"description":"Created media row (database field `size`, plus processing fields)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"integer"},"filename":{"type":"string"},"originalName":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"integer"},"url":{"type":"string","format":"uri"},"source":{"type":"string","enum":["upload","ai","wordpress"]},"processingStatus":{"type":"string"},"createdAt":{"type":"integer"}}}}}},"400":{"description":"No file, invalid file type, or a JSON body that fails validation (then `error` holds the Zod issues); malformed JSON returns 500","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"object","additionalProperties":true}}]}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/multipart/create":{"post":{"tags":["Media"],"summary":"Start multipart video upload","description":"Starts a direct-to-bucket multipart upload for a video (MP4, QuickTime, WebM). Upload the parts with presigned URLs from `/api/media/multipart/part-url`, then call `/complete` and register the file with `/api/media/confirm`. Requires a plan with video uploads.","operationId":"createMultipartUpload","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","contentType","fileSize"],"properties":{"filename":{"type":"string","minLength":1},"contentType":{"type":"string","pattern":"^video/(mp4|quicktime|webm)$"},"fileSize":{"type":"number","exclusiveMinimum":0,"description":"Bytes"}}}}}},"responses":{"200":{"description":"Upload started","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"uploadId":{"type":"string"},"publicUrl":{"type":"string","format":"uri"},"partSize":{"type":"integer","description":"Bytes per part (64 MiB)"},"partCount":{"type":"integer"},"threshold":{"type":"integer","description":"Below this size a simple presigned upload is enough (100 MiB)"}}}}}},"400":{"description":"Validation failed (`details` holds the Zod issues), file larger than the video limit (default 4 GiB), or too many parts (`error` text only)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"Plan does not include video uploads","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"requiredTier":{"type":["string","null"]}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/multipart/part-url":{"post":{"tags":["Media"],"summary":"Presigned URL for one part","operationId":"getMultipartPartUrl","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","uploadId","partNumber"],"properties":{"key":{"type":"string","minLength":1},"uploadId":{"type":"string","minLength":1},"partNumber":{"type":"integer","minimum":1,"maximum":10000}}}}}},"responses":{"200":{"description":"Presigned PUT URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"partNumber":{"type":"integer"},"expiresIn":{"type":"integer","description":"Seconds"}}}}}},"400":{"description":"Validation failed (`details` holds the Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"The upload key does not belong to the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/multipart/complete":{"post":{"tags":["Media"],"summary":"Complete multipart upload","description":"Assembles the uploaded parts. Does not create a media item; register the file with `/api/media/confirm`.","operationId":"completeMultipartUpload","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","uploadId","parts"],"properties":{"key":{"type":"string","minLength":1},"uploadId":{"type":"string","minLength":1},"parts":{"type":"array","minItems":1,"maxItems":10000,"items":{"type":"object","required":["partNumber","etag"],"properties":{"partNumber":{"type":"integer","minimum":1,"maximum":10000},"etag":{"type":"string","minLength":1}}}}}}}}},"responses":{"200":{"description":"Assembled","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"publicUrl":{"type":"string","format":"uri"}}}}}},"400":{"description":"Validation failed (`details` holds the Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"The upload key does not belong to the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Assembly failed; the upload was aborted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/multipart/abort":{"post":{"tags":["Media"],"summary":"Abort multipart upload","description":"Frees the parts of an unfinished upload. An unknown or finished upload also returns 200.","operationId":"abortMultipartUpload","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","uploadId"],"properties":{"key":{"type":"string","minLength":1},"uploadId":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Aborted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Validation failed (`details` holds the Zod issues)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"The upload key does not belong to the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/presign":{"post":{"tags":["Media"],"summary":"Get presigned upload URL","description":"Returns a presigned S3 URL for direct upload. Upload the file via PUT to the returned URL, then call `/api/media/confirm`.","operationId":"presignMediaUpload","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","contentType"],"properties":{"filename":{"type":"string"},"contentType":{"type":"string","description":"MIME type (must be image/*)"},"fileSize":{"type":"integer","description":"File size in bytes (max 50 MB)"}}}}}},"responses":{"200":{"description":"Presigned URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string","format":"uri","description":"PUT the file to this URL"},"publicUrl":{"type":"string","format":"uri","description":"Public URL after upload"},"key":{"type":"string","description":"S3 object key (pass to /confirm)"},"expiresIn":{"type":"integer","description":"URL expiration in seconds"}}}}}},"400":{"description":"Invalid file type or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/confirm":{"post":{"tags":["Media"],"summary":"Confirm S3 upload","description":"Saves media metadata after a successful S3 upload via presigned URL.","operationId":"confirmMediaUpload","x-required-scope":"media:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","publicUrl","filename","contentType","fileSize"],"properties":{"key":{"type":"string","description":"S3 object key from /presign response"},"publicUrl":{"type":"string","format":"uri"},"filename":{"type":"string"},"contentType":{"type":"string"},"fileSize":{"type":"integer"},"altText":{"type":"string"}}}}}},"responses":{"200":{"description":"Upload confirmed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"media":{"$ref":"#/components/schemas/MediaItem"}}}}}},"403":{"description":"Invalid upload key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/media/generate":{"post":{"tags":["Media"],"summary":"Generate AI image","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Generates an image using AI (OpenAI gpt-image-1) and stores it in S3.","operationId":"generateMediaImage","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","description":"Image generation prompt"},"size":{"type":"string","enum":["1024x1024","1536x1024","1024x1536"],"default":"1024x1024"},"quality":{"type":"string","enum":["standard","hd"],"default":"standard"},"model":{"type":"string","description":"Model ID (default: gpt-image-1)"},"title":{"type":"string"},"altText":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Image generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaItem"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/team":{"get":{"tags":["Team"],"summary":"List team members","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Returns the team members of the publisher by role, the publisher's pending (not accepted, not expired) team invitations and the roles that can currently be invited (active roles only).","operationId":"listTeamMembers","responses":{"200":{"description":"Team members, pending invitations and invitable roles","content":{"application/json":{"schema":{"type":"object","properties":{"authors":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"agencies":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"customers":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"pendingInvitations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"email":{"type":"string"},"role":{"type":"string"},"expiresAt":{"type":"integer","description":"Unix timestamp (seconds)"}}}},"invitableRoles":{"type":"array","items":{"type":"string","enum":["customer","agency"]}}}}}}},"403":{"description":"No permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Team"],"summary":"Invite team member","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Invites a user by email. Existing and new addresses get the same pending invitation and the same answer; the person joins only after accepting it. Inactive roles are rejected. Limited per publisher (50 per hour, 300 per day).","operationId":"inviteTeamMember","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["customer","agency"]}}}}}},"responses":{"200":{"description":"Existing pending invitation resent (same body as 201, type invitation_resent)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"type":{"type":"string","enum":["invitation_resent"]}}}}}},"201":{"description":"Invitation sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"type":{"type":"string","enum":["invitation_sent","invitation_resent"]},"emailSent":{"type":"boolean"},"email":{"type":"string"},"role":{"type":"string"}}}}}},"403":{"description":"No permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already in team, or role_inactive (role currently not assignable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"invite_rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/team/{id}":{"delete":{"tags":["Team"],"summary":"Remove team member","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"removeTeamMember","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"403":{"description":"No permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/organizations":{"get":{"tags":["Organizations"],"summary":"List organizations","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Returns all organizations the authenticated user is a member of.","operationId":"listOrganizations","responses":{"200":{"description":"List of organizations","content":{"application/json":{"schema":{"type":"object","properties":{"organizations":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Organization"},{"type":"object","properties":{"memberCount":{"type":"integer"}}}]}}}}}}}}},"post":{"tags":["Organizations"],"summary":"Create organization","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"createOrganization","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"website":{"type":"string","format":"uri"},"logoUrl":{"type":"string","format":"uri"},"industry":{"type":"string"},"companySize":{"type":"string","enum":["micro","small","medium","large"]},"description":{"type":"string"},"targetAudience":{"type":"string"},"brandVoice":{"type":"string"},"keyTopics":{"type":"array","items":{"type":"string"}},"approvalMode":{"type":"string","enum":["none","one","all","custom"],"default":"none"},"approvalMinCount":{"type":"integer"}}}}}},"responses":{"201":{"description":"Organization created","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/Organization"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"#1307: `customer_org_scope_unverified` (the active organization is missing, deleted, or the account is not its owner/admin) or `customer_org_name_exists` (a customer company or customer org of the same name exists for this organization). Nothing is created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/organizations/{id}":{"get":{"tags":["Organizations"],"summary":"Get organization details","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Returns organization with members and campaign count.","operationId":"getOrganization","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Organization details","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"allOf":[{"$ref":"#/components/schemas/Organization"},{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"campaignCount":{"type":"integer"}}}]}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Organizations"],"summary":"Update organization","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard).","operationId":"updateOrganization","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"website":{"type":"string","format":"uri"},"logoUrl":{"type":"string","format":"uri"},"industry":{"type":"string"},"companySize":{"type":"string","enum":["micro","small","medium","large"]},"description":{"type":"string"},"targetAudience":{"type":"string"},"brandVoice":{"type":"string"},"keyTopics":{"type":"array","items":{"type":"string"}},"approvalMode":{"type":"string","enum":["none","one","all","custom"]},"approvalMinCount":{"type":"integer"}}}}}},"responses":{"200":{"description":"Organization updated","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/Organization"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Organizations"],"summary":"Delete organization","security":[{"SessionCookie":[]}],"x-auth":"session","description":"Session only (Dashboard). Deletes the organization. Fails if organization is used in active campaigns.","operationId":"deleteOrganization","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Organization deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Used in active campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders":{"get":{"tags":["Orders"],"summary":"List orders","description":"Returns publisher orders. Requires `orders:read`.","operationId":"listOrders","x-required-scope":"orders:read","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["draft","in_progress","pending_approval","revision_requested","approved","published","invoiced","paid","completed","cancelled"]}},{"name":"customerOrgId","in":"query","schema":{"type":"integer"}},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated list of orders","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Orders"],"summary":"Create order","description":"Creates an empty order wrapper for a customer organization. Requires `orders:write`.","operationId":"createOrder","x-required-scope":"orders:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customerOrganizationId"],"properties":{"customerOrganizationId":{"type":"integer"},"description":{"type":"string"},"externalReference":{"type":"string","description":"Order-wide external reference"},"notes":{"type":"string"},"currency":{"type":"string","default":"EUR"}}}}}},"responses":{"201":{"description":"Order created","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}},"404":{"description":"Customer organization not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}":{"get":{"tags":["Orders"],"summary":"Get order details","description":"Returns the order plus customer organization, linked content/campaign (incl. articles) and the Claribill invoice URL. Requires `orders:read`.","operationId":"getOrder","x-required-scope":"orders:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Order details","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"claribillInvoiceUrl":{"type":"string","nullable":true}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Orders"],"summary":"Update order / link content","description":"Updates order metadata or links a post. Linking behavior: the first `contentId` links directly; a second post automatically converts the order into an invisible container campaign; further posts append as articles. `contentExternalReference` sets the per-position reference for the newly linked post. Requires `orders:write`.","operationId":"updateOrder","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"externalReference":{"type":"string"},"notes":{"type":"string"},"customerOrganizationId":{"type":"integer","description":"Only changeable while status=draft"},"contentId":{"type":"integer","nullable":true},"campaignId":{"type":"integer","nullable":true},"contentExternalReference":{"type":"string","maxLength":250,"description":"Per-position reference for the post linked via contentId"}}}}}},"responses":{"200":{"description":"Order updated","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}},"422":{"description":"Invalid link (e.g. duplicate post, wrong customer)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Orders"],"summary":"Delete order (soft delete)","operationId":"deleteOrder","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Order deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/status":{"post":{"tags":["Orders"],"summary":"Change order status","description":"Moves the order through its lifecycle (draft → in_progress → [pending_approval → approved] → published → invoiced → paid → completed; cancelled). `in_progress` requires linked content, a campaign or an external contribution line item. Requires `orders:write`.","operationId":"setOrderStatus","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["draft","in_progress","pending_approval","revision_requested","approved","published","invoiced","paid","completed","cancelled"]},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"Status updated","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}},"422":{"description":"Invalid transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/line-items":{"get":{"tags":["Orders"],"summary":"List order line items","description":"Positions (posts/channels, external contributions, add-ons, discounts). Requires `orders:read`.","operationId":"listOrderLineItems","x-required-scope":"orders:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Line items","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderLineItem"}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Orders"],"summary":"Add line item (external post, add-on, discount)","description":"Adds a position. For an off-platform post use `source: \"external\"` with `externalUrl`, the customer price as `unitAmount` (cents) and optionally `internalCost` (internal purchase cost, never on the invoice) and `externalReference`. Adding an external item to a draft order auto-starts it. Requires `orders:write`.","operationId":"addOrderLineItem","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","unitAmount"],"properties":{"lineType":{"type":"string","enum":["PRODUCT","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE"],"default":"PRODUCT"},"source":{"type":"string","enum":["external","addon","discount"],"default":"addon"},"label":{"type":"string","maxLength":500},"quantity":{"type":"integer","default":1},"unitAmount":{"type":"integer","description":"Cents (or basis points for DISCOUNT_PERCENT)"},"taxRate":{"type":"integer","description":"Basis points; defaults to the order snapshot"},"externalUrl":{"type":"string","format":"uri","description":"source=external: URL of the published post"},"externalReference":{"type":"string","maxLength":250},"internalCost":{"type":"integer","minimum":0,"description":"Internal cost in cents — never on the customer invoice"}}}}}},"responses":{"201":{"description":"Line item created (totals recomputed)","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"$ref":"#/components/schemas/OrderLineItem"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/line-items/{itemId}":{"patch":{"tags":["Orders"],"summary":"Update line item","operationId":"updateOrderLineItem","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"quantity":{"type":"integer"},"unitAmount":{"type":"integer"},"taxRate":{"type":"integer"},"externalUrl":{"type":"string","nullable":true},"externalReference":{"type":"string","nullable":true},"internalCost":{"type":"integer","nullable":true}}}}}},"responses":{"200":{"description":"Line item updated (totals recomputed)","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"$ref":"#/components/schemas/OrderLineItem"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Orders"],"summary":"Delete line item","operationId":"deleteOrderLineItem","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Line item deleted (totals recomputed)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/invoice":{"post":{"tags":["Orders"],"summary":"Create Claribill invoice","description":"Creates the multi-line Claribill invoice from the order line items (internal costs are never included). Options: `draft: true` creates a draft only; `external: true` marks the order as externally invoiced. Without a stored Claribill customer, pass `claribillCustomerId` or `createCustomer: true` (confirm via the customer-match endpoint first). Requires `orders:invoice`.","operationId":"createOrderInvoice","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"claribillCustomerId":{"type":"string"},"createCustomer":{"type":"boolean"},"draft":{"type":"boolean"},"external":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Invoice created (or order marked external)","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"invoice":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"draft":{"type":"boolean"},"mock":{"type":"boolean"}}}}}}}},"409":{"description":"Another concurrent request is already resolving/creating the Claribill customer for this org (`claribill_customer_claim_conflict`, retryable after ~2 minutes; #631), or the matching Claribill customer is archived (`claribill_customer_archived`) or belongs to another organization (`claribill_customer_identity_conflict`; #887, not retryable without a data change)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Order not invoiceable (wrong status, active invoice, missing customer confirmation), or the customer organization has no email address for `createCustomer: true` (`claribill_customer_missing_email`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/invoice/create-customer":{"post":{"tags":["Orders"],"summary":"Resolve or create the Claribill customer for an order's organization","description":"Separate step before invoicing: links the order's customer organization to a Claribill customer and persists the id, serialized through an org-wide atomic claim (`claribill_customer_claim_token`/`_expires_at`, #631) so two concurrent requests for the same unlinked organization cannot create duplicate Claribill customers or overwrite each other's persisted id. Pass `claribillCustomerId` for a UI-confirmed match, or omit it to search by email and, after UI confirmation, create a new one. Requires `orders:invoice`.","operationId":"createOrderInvoiceCustomer","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"claribillCustomerId":{"type":"string"}}}}}},"responses":{"200":{"description":"Resolved (customerId, created, alreadyLinked, vatIdBackfilled)","content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"created":{"type":"boolean"},"alreadyLinked":{"type":"boolean"},"vatIdBackfilled":{"type":"boolean"}}}}}},"409":{"description":"Another concurrent request is already resolving/creating the Claribill customer for this org (`claribill_customer_claim_conflict`, retryable after ~2 minutes), or the matching Claribill customer is archived (`claribill_customer_archived`) or belongs to another organization (`claribill_customer_identity_conflict`; #887)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Claribill not configured, order has no customer organization, or the organization has no email address (`claribill_customer_missing_email`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/invoice/customer-match":{"get":{"tags":["Orders"],"summary":"Match order customer against Claribill","description":"Checks whether the order's customer organization is linked to / found in Claribill. Exactly one customer with the organization's email → `match`; several customers sharing that email (or the same name in the name fallback) → `matches` for explicit selection. Includes a `taxReview` block when the customer's EU VAT ID suggests reverse charge but the order is set up with full VAT. Requires `orders:invoice`.","operationId":"matchOrderInvoiceCustomer","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Match result (configured/linked/match/matches/taxReview/noEmail/orgData)"},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/tax-review":{"get":{"tags":["Orders"],"summary":"Review order VAT against the customer's VAT ID","description":"Compares the order's VAT snapshot with the authoritative customer VAT ID (organization, falling back to the linked Claribill customer). Returns a reverse-charge suggestion when the customer has an EU VAT ID from another country than the issuer but the order is set up with full VAT. Read-only. Requires `orders:invoice`.","operationId":"getOrderTaxReview","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Review result (current, vatId, vatSource, suggestion)"},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Orders"],"summary":"Apply reverse-charge suggestion","description":"Applies the reverse-charge suggestion after validating the VAT ID via VIES (invalid → 422 `vat_id_invalid`): updates the customer organization defaults (backfills an empty VAT ID from Claribill), fixes the order's VAT snapshot and recomputes the totals. Requires `orders:invoice`.","operationId":"applyOrderTaxReview","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Applied (order, totals, viesStatus, vatId)","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"viesStatus":{"type":"string","enum":["valid","unavailable"]},"vatId":{"type":"string"},"vatCountry":{"type":"string"}}}}}},"422":{"description":"No suggestion (`no_tax_suggestion`), invalid VAT ID (`vat_id_invalid`) or terminal order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/invoice/sync":{"post":{"tags":["Orders"],"summary":"Sync invoice status from Claribill","description":"Pulls the current invoice status (and final invoice number) from Claribill. Requires `orders:invoice`.","operationId":"syncOrderInvoice","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Sync result","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"sync":{"type":"object"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/invoice/send-status":{"get":{"tags":["Orders"],"summary":"Read the stored invoice Send receipt","operationId":"getOrderInvoiceSendStatus","x-required-scope":"orders:invoice","description":"Reads the original CMM Send observation without provider calls. ACCEPTED is transport acceptance, not delivery. Aftercare is separate; resolved:true does not convert UNKNOWN into acceptance. Requires `orders:invoice`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{data:null|{state,aftercare,resolved,checkedAtMs,code,invoiceNumber?,attemptId?}}; no key, request or recipient data"},"403":{"description":"Scope or order access denied"},"503":{"description":"Integration or receipt unavailable"}}},"post":{"tags":["Orders"],"summary":"Check the original invoice Send status","operationId":"checkOrderInvoiceSendStatus","x-required-scope":"orders:invoice","description":"Only GETs Claribill's send-status using the original persisted key and verifies the current company binding. Never sends, retries, creates a key, or uses invoice status as acceptance evidence. Requires `orders:invoice`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"action":{"type":"string","enum":["check"]}}}}}},"responses":{"200":{"description":"Updated stored observation or null for an invoice without a Send receipt"},"400":{"description":"Only action:check is accepted"},"403":{"description":"Scope or order access denied"},"503":{"description":"Integration or receipt unavailable"}}}},"/api/orders/{id}/invoice/reconcile":{"post":{"tags":["Orders"],"summary":"Resolve an order stuck on invoiceStatus \"unclear\" (#619)","description":"Three escape hatches for an ambiguous Claribill invoice create/send result that the strict reconciliation could not uniquely resolve: `recheck` (re-run the same strict match), `link` (verify and attach a Claribill invoice id the caller names), or `reset` (restore the pre-claim status — requires `confirmNoInvoiceExists: true`, and is itself refused if a possible non-cancelled invoice for this order is still found in Claribill). Requires `orders:invoice`.","operationId":"reconcileUnclearOrderInvoice","x-required-scope":"orders:invoice","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"action":{"type":"string","enum":["recheck"]}},"required":["action"]},{"type":"object","properties":{"action":{"type":"string","enum":["link"]},"claribillInvoiceId":{"type":"string"}},"required":["action","claribillInvoiceId"]},{"type":"object","properties":{"action":{"type":"string","enum":["reset"]},"confirmNoInvoiceExists":{"type":"boolean","enum":[true]}},"required":["action","confirmNoInvoiceExists"]}]}}}},"responses":{"200":{"description":"Resolved (or still unclear for `recheck`)","content":{"application/json":{"schema":{"type":"object","properties":{"resolved":{"type":"boolean"},"matchCount":{"type":"integer"},"priorStatus":{"type":"string"},"invoice":{"type":"object"},"order":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"description":"Missing/invalid action, claribillInvoiceId, or confirmation"},"404":{"description":"Order not found"},"409":{"description":"Verification mismatch, a possible existing invoice was found (reset), or a concurrent modification"},"422":{"description":"Order is not currently \"unclear\", Claribill not configured, or no linked customer"},"502":{"description":"The Claribill lookup itself failed"},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/stats":{"get":{"tags":["Orders"],"summary":"Order KPI statistics","description":"Aggregated counts and revenue. Requires `orders:read`.","operationId":"getOrderStats","x-required-scope":"orders:read","responses":{"200":{"description":"Stats"},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/publishing-channels":{"get":{"tags":["Publishing Channels"],"summary":"List publishing channels","description":"Channels of the organization plus own legacy channels, with usage counts. With an API key that also holds `channels:write`, missing channel rows of connected sites are created first (self-heal).","operationId":"listPublishingChannels","x-required-scope":"channels:read","responses":{"200":{"description":"Channels","content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"allOf":[{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string","enum":["wordpress","custom_api"]},"name":{"type":"string"},"siteUrl":{"type":"string","format":"uri"},"userId":{"type":["string","null"]},"orgId":{"type":["string","null"],"format":"uuid"},"status":{"type":"string","enum":["active","inactive","error"]},"topicKeywords":{"type":"array","items":{"type":"string"}},"primaryLanguage":{"type":"string"},"allowedLanguages":{"type":"array","items":{"type":"string"}},"allowMismatchedLanguages":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true},"deletedAt":{"type":["integer","null"]},"deletedBy":{"type":["string","null"]},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}}},{"type":"object","properties":{"wordPressSiteId":{"type":["integer","null"]},"usageCount":{"type":"integer"}}}]}},"_debug":{"type":"object","additionalProperties":true,"description":"Diagnostics, not a stable contract"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Publishing Channels"],"summary":"Create publishing channel","description":"Creates a WordPress channel (`authToken` required, `username:applicationPassword`) or a custom API channel (`apiKey` and `apiEndpoint` required). Credentials are never returned in responses.","operationId":"createPublishingChannel","x-required-scope":"channels:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","name","siteUrl"],"properties":{"type":{"type":"string","enum":["wordpress","custom_api"]},"name":{"type":"string","minLength":1,"maxLength":200},"siteUrl":{"type":"string","format":"uri"},"authToken":{"type":"string","description":"Required for `wordpress`"},"apiKey":{"type":"string","description":"Required for `custom_api`"},"apiEndpoint":{"type":"string","format":"uri","description":"Required for `custom_api`"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string","enum":["wordpress","custom_api"]},"name":{"type":"string"},"siteUrl":{"type":"string","format":"uri"},"userId":{"type":["string","null"]},"orgId":{"type":["string","null"],"format":"uuid"},"status":{"type":"string","enum":["active","inactive","error"]},"topicKeywords":{"type":"array","items":{"type":"string"}},"primaryLanguage":{"type":"string"},"allowedLanguages":{"type":"array","items":{"type":"string"}},"allowMismatchedLanguages":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true},"deletedAt":{"type":["integer","null"]},"deletedBy":{"type":["string","null"]},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}}}}}}}},"400":{"description":"Validation failed or credentials missing for the channel type","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"500":{"description":"Channel could not be created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/dashboard/stats":{"get":{"tags":["Dashboard"],"summary":"Dashboard statistics","description":"Content, site and order statistics for the caller's access scope (administrators: all content and sites; orders always of the caller's organization).","operationId":"getDashboardStats","x-required-scope":"dashboard:read","responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"object","properties":{"total":{"type":"integer"},"thisWeek":{"type":"integer","description":"Last 7 days"},"thisMonth":{"type":"integer","description":"Last 30 days"},"publishedThisMonth":{"type":"integer"},"byStatus":{"type":"object","properties":{"draft":{"type":"integer"},"for_review":{"type":"integer"},"approved":{"type":"integer"},"scheduled":{"type":"integer"},"published":{"type":"integer"},"rejected":{"type":"integer"}}}}},"sites":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"}}},"orders":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"},"unpaidInvoices":{"type":"integer"},"revenueThisMonth":{"type":"string","description":"Sum of agreed prices of paid orders this month (cents, as text)"}}},"recentContent":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"status":{"type":"string"},"createdAt":{"type":"integer"},"siteName":{"type":["string","null"]}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/accounts":{"get":{"tags":["Social"],"summary":"List social accounts","description":"Connected social accounts grouped by publishing channel (no-channel group last). Tokens are never returned.","operationId":"listSocialAccounts","x-required-scope":"social:read","parameters":[{"name":"channelId","in":"query","schema":{"type":"integer","minimum":1}},{"name":"network","in":"query","schema":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Accounts by channel","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"groups":{"type":"array","items":{"type":"object","properties":{"channelId":{"type":["integer","null"]},"channelName":{"type":["string","null"]},"channelSiteUrl":{"type":["string","null"]},"channelType":{"type":["string","null"]},"accounts":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"networkLabel":{"type":"string"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"profileUrl":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"status":{"type":"string"},"statusReason":{"type":["string","null"]},"capabilities":{"type":"object","additionalProperties":true},"grant":{"type":["object","null"],"additionalProperties":true,"description":"OAuth grant metadata, no tokens"},"health":{"type":["object","null"],"additionalProperties":true}}}}}}}}}}}},"400":{"description":"Invalid input (`details` holds the Zod issues) or unparseable body","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"Social module disabled (`social_disabled`) or missing permission","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts":{"get":{"tags":["Social"],"summary":"List social posts","operationId":"listSocialPosts","x-required-scope":"social:read","parameters":[{"name":"status","in":"query","schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"integer","minimum":0},"description":"Filter on `scheduledAt`"},{"name":"to","in":"query","schema":{"type":"integer","minimum":0}},{"name":"channelId","in":"query","schema":{"type":"integer","minimum":1}},{"name":"accountId","in":"query","schema":{"type":"integer","minimum":1}},{"name":"contentId","in":"query","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Posts by `scheduledAt` descending (unscheduled drafts first), then id descending","content":{"application/json":{"schema":{"type":"object","properties":{"posts":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"integer"},"label":{"type":["string","null"]},"baseText":{"type":"string"},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"approvalStatus":{"type":["string","null"]},"counts":{"type":"object","properties":{"targets":{"type":"integer"},"published":{"type":"integer"},"failed":{"type":"integer"}}},"targets":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"400":{"description":"Invalid input (`details` holds the Zod issues) or unparseable body","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"403":{"description":"Social module disabled or missing permission","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Social"],"summary":"Create social post","description":"Creates a draft (or `pending_approval` with `requiresApproval`). Without `publishingChannelId` the channel is derived when all target accounts share one.","operationId":"createSocialPost","x-required-scope":"social:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targets"],"properties":{"baseText":{"type":"string","maxLength":25000,"default":""},"publishingChannelId":{"type":["integer","null"],"minimum":1},"contentId":{"type":["integer","null"],"minimum":1},"campaignId":{"type":["integer","null"],"minimum":1},"label":{"type":"string","maxLength":200},"linkUrl":{"type":["string","null"],"format":"uri","maxLength":2048},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}},"scheduledAt":{"type":["integer","null"],"minimum":1,"description":"Stored only; scheduling happens via `/schedule`"},"timezone":{"type":"string","maxLength":64,"default":"Europe/Vienna"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"],"minimum":0,"maximum":2592000},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"],"maxLength":128},"status":{"type":"string","enum":["draft","pending_approval","scheduled"],"description":"`scheduled` is stored as `draft`"},"aiGenerated":{"type":"boolean"},"targets":{"type":"array","minItems":1,"maxItems":25,"items":{"type":"object","required":["accountId","network"],"properties":{"accountId":{"type":"integer","minimum":1},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"],"maxLength":25000},"options":{"type":"object","additionalProperties":true,"required":["network"],"description":"Network-specific options; must repeat `network` (discriminated union, strict per network)","properties":{"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]}}},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}}}}}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}}}}}}},"400":{"description":"Invalid input, `duplicate_target`, `network_mismatch` or `channel_invariant_violated`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"402":{"description":"Plan upgrade required (`upgrade_required`)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"403":{"description":"`target_account_forbidden`, `media_forbidden`, `channel_forbidden` or module disabled","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`channel_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"422":{"description":"Validation failed (`code: validation_failed`) or no targets (`no_targets`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"allIssues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"targets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"integer","description":"Array index on create, target id on existing posts"},"targetId":{"type":["integer","null"]},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"accountName":{"type":["string","null"]}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/validate":{"post":{"tags":["Social"],"summary":"Validate social post","description":"Validates a post body like create, without writing. Blocking findings are a 200 result, not an error.","operationId":"validateSocialPost","x-required-scope":"social:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targets"],"properties":{"baseText":{"type":"string","maxLength":25000,"default":""},"publishingChannelId":{"type":["integer","null"],"minimum":1},"contentId":{"type":["integer","null"],"minimum":1},"campaignId":{"type":["integer","null"],"minimum":1},"label":{"type":"string","maxLength":200},"linkUrl":{"type":["string","null"],"format":"uri","maxLength":2048},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}},"scheduledAt":{"type":["integer","null"],"minimum":1,"description":"Stored only; scheduling happens via `/schedule`"},"timezone":{"type":"string","maxLength":64,"default":"Europe/Vienna"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"],"minimum":0,"maximum":2592000},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"],"maxLength":128},"status":{"type":"string","enum":["draft","pending_approval","scheduled"],"description":"`scheduled` is stored as `draft`"},"aiGenerated":{"type":"boolean"},"targets":{"type":"array","minItems":1,"maxItems":25,"items":{"type":"object","required":["accountId","network"],"properties":{"accountId":{"type":"integer","minimum":1},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"],"maxLength":25000},"options":{"type":"object","additionalProperties":true,"required":["network"],"description":"Network-specific options; must repeat `network` (discriminated union, strict per network)","properties":{"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]}}},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}}}}}}}}}},"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","properties":{"publishable":{"type":"boolean"},"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"blocking":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"targets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"integer","description":"Array index on create, target id on existing posts"},"targetId":{"type":["integer","null"]},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"accountName":{"type":["string","null"]}}}}}}}}},"400":{"description":"Invalid input or `network_mismatch`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"403":{"description":"`target_account_forbidden`, `media_forbidden` (media of another tenant) or module disabled","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/from-content/{contentId}":{"post":{"tags":["Social"],"summary":"Create social post from article","description":"Creates a draft for all active accounts of the article's channel (or the given ones). `linkUrl` may be the placeholder `{{articleUrl}}`, filled on publish.","operationId":"createSocialPostFromContent","x-required-scope":"social:write","parameters":[{"name":"contentId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"accountIds":{"type":"array","maxItems":25,"items":{"type":"integer","minimum":1}},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":"integer","minimum":0,"maximum":2592000},"label":{"type":"string","maxLength":200}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}},"derivedFrom":{"type":"object","properties":{"contentId":{"type":"integer"},"title":{"type":"string"},"channelId":{"type":["integer","null"]},"targets":{"type":"integer"},"usesArticleUrlPlaceholder":{"type":"boolean"}}}}}}}},"400":{"description":"`invalid_content_id` or invalid input","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"402":{"description":"Plan upgrade required","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`content_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"422":{"description":"`no_social_accounts`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}":{"get":{"tags":["Social"],"summary":"Get social post","operationId":"getSocialPost","x-required-scope":"social:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Post with events and validation","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}},"events":{"type":"array","maxItems":100,"items":{"type":"object","additionalProperties":true}},"validation":{"type":"object","properties":{"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"publishable":{"type":"boolean"}}}}}}}},"400":{"description":"`invalid_post_id`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"patch":{"tags":["Social"],"summary":"Update social post","description":"Partial update with optimistic lock (`scheduleVersion`): omitted fields stay unchanged (no defaults are applied, #1264). `targets` replaces the whole list; `status` and `scheduledAt` are ignored here. A scheduled post is rescheduled.","operationId":"updateSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"baseText":{"type":"string","maxLength":25000},"publishingChannelId":{"type":["integer","null"],"minimum":1},"contentId":{"type":["integer","null"],"minimum":1},"campaignId":{"type":["integer","null"],"minimum":1},"label":{"type":"string","maxLength":200},"linkUrl":{"type":["string","null"],"format":"uri","maxLength":2048},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}},"scheduledAt":{"type":["integer","null"],"minimum":1,"description":"Stored only; scheduling happens via `/schedule`"},"timezone":{"type":"string","maxLength":64},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"],"minimum":0,"maximum":2592000},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"],"maxLength":128},"status":{"type":"string","enum":["draft","pending_approval","scheduled"],"description":"`scheduled` is stored as `draft`"},"aiGenerated":{"type":"boolean"},"targets":{"type":"array","minItems":1,"maxItems":25,"items":{"type":"object","required":["accountId","network"],"properties":{"accountId":{"type":"integer","minimum":1},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"],"maxLength":25000},"options":{"type":"object","additionalProperties":true,"required":["network"],"description":"Network-specific options; must repeat `network` (discriminated union, strict per network)","properties":{"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]}}},"mediaIds":{"type":"array","maxItems":35,"description":"Media ids or selections; a (mediaId, role) pair may appear once","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","additionalProperties":false,"required":["mediaId"],"properties":{"mediaId":{"type":"integer","minimum":1},"cropRect":{"type":["object","null"],"additionalProperties":false,"required":["x","y","width","height"],"properties":{"x":{"type":"number","minimum":0},"y":{"type":"number","minimum":0},"width":{"type":"number","exclusiveMinimum":0},"height":{"type":"number","exclusiveMinimum":0}}},"altText":{"type":["string","null"],"maxLength":5000},"role":{"type":"string","enum":["primary","carousel_item","cover","thumbnail","link_thumb"]},"position":{"type":"integer","minimum":0}}}]}}}}},"scheduleVersion":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}}}}}}},"400":{"description":"Invalid input, target errors, `schedule_too_soon` or `schedule_too_far`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"403":{"description":"Account, media or channel not accessible (`channel_forbidden`)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found` or `channel_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_not_editable`, `stale_post_version`, `published_target_immutable` or `schedule_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"422":{"description":"Validation failed (`code: validation_failed`) or no targets (`no_targets`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"allIssues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"targets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"integer","description":"Array index on create, target id on existing posts"},"targetId":{"type":["integer","null"]},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"accountName":{"type":["string","null"]}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"delete":{"tags":["Social"],"summary":"Cancel social post","description":"Soft delete: the post becomes `cancelled` and open targets are cancelled. Nothing is deleted on the networks (see the remote target delete).","operationId":"deleteSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"postId":{"type":"integer"},"cancelledTargets":{"type":"integer"},"scheduleVersion":{"type":"integer"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_publishing` or `delete_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/approve":{"post":{"tags":["Social"],"summary":"Approve social post","operationId":"approveSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"notes":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Approved","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_not_approvable` or `approval_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/reject":{"post":{"tags":["Social"],"summary":"Reject social post","description":"Back to `draft` with `approvalStatus: rejected`; a scheduled post is unscheduled.","operationId":"rejectSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"notes":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Rejected","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_not_rejectable` or `approval_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/duplicate":{"post":{"tags":["Social"],"summary":"Duplicate social post","operationId":"duplicateSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"201":{"description":"Copy created as draft","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}}}}}}},"402":{"description":"Plan upgrade required","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"403":{"description":"`target_account_forbidden`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/publish-now":{"post":{"tags":["Social"],"summary":"Publish social post now","description":"Queues the post for immediate publishing (background job). X credits are checked here and charged by the job.","operationId":"publishSocialPostNow","x-required-scope":"social:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}},"scheduleVersion":{"type":"integer"},"accepted":{"type":"boolean"},"xBudget":{"type":["object","null"],"description":"X (Twitter) credit check; credits are charged by the publish job","properties":{"required":{"type":"number"},"includedRemaining":{"type":"number"},"creditBalance":{"type":"number"},"targets":{"type":"integer"}}}}}}}},"402":{"description":"`x_budget_exhausted` or plan upgrade required","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_not_publishable`, `approval_required` or `schedule_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"422":{"description":"Validation failed (`code: validation_failed`) or no targets (`no_targets`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"allIssues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"targets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"integer","description":"Array index on create, target id on existing posts"},"targetId":{"type":["integer","null"]},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"accountName":{"type":["string","null"]}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/schedule":{"post":{"tags":["Social"],"summary":"Schedule social post","description":"Schedules the post (at least 60 s, at most 365 days ahead). X credits are checked here and charged by the publish job.","operationId":"scheduleSocialPost","x-required-scope":"social:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduledAt"],"properties":{"scheduledAt":{"type":["integer","null"],"minimum":1,"description":"Unix seconds; `null` is refused (`use_unschedule`)"},"timezone":{"type":"string","maxLength":64},"scheduleVersion":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"Scheduled","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}},"scheduleVersion":{"type":"integer"},"scheduledAt":{"type":"integer"},"scheduledTargets":{"type":"integer"},"xBudget":{"type":["object","null"],"description":"X (Twitter) credit check; credits are charged by the publish job","properties":{"required":{"type":"number"},"includedRemaining":{"type":"number"},"creditBalance":{"type":"number"},"targets":{"type":"integer"}}}}}}}},"400":{"description":"`use_unschedule`, `schedule_too_soon`, `schedule_too_far` or invalid input","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"402":{"description":"`x_budget_exhausted` or plan upgrade required","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_not_schedulable`, `schedule_conflict` or `approval_required`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"422":{"description":"Validation failed (`code: validation_failed`) or no targets (`no_targets`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"issues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"allIssues":{"type":"object","description":"Keyed by target key (array index on create, target id on existing posts)","additionalProperties":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"blocking":{"type":"boolean"}}}}},"targets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"integer","description":"Array index on create, target id on existing posts"},"targetId":{"type":["integer","null"]},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"accountName":{"type":["string","null"]}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/unschedule":{"post":{"tags":["Social"],"summary":"Unschedule social post","operationId":"unscheduleSocialPost","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Back to draft","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"integer"},"orgId":{"type":["string","null"]},"userId":{"type":["string","null"]},"publishingChannelId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"campaignId":{"type":["integer","null"]},"label":{"type":["string","null"]},"baseText":{"type":"string"},"linkUrl":{"type":["string","null"]},"status":{"type":"string"},"scheduledAt":{"type":["integer","null"],"description":"Unix seconds"},"scheduleVersion":{"type":"integer"},"timezone":{"type":"string"},"publishAfterContent":{"type":"boolean"},"publishAfterOffsetSeconds":{"type":["integer","null"]},"publishStartedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"completedAt":{"type":["integer","null"],"description":"Unix seconds"},"publishAttempts":{"type":"integer"},"lastError":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"customerUserId":{"type":["string","null"]},"approvalStatus":{"type":["string","null"]},"approvedAt":{"type":["integer","null"],"description":"Unix seconds"},"approvedBy":{"type":["string","null"]},"approvalNotes":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":true},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"},"targets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"targetId":{"type":["integer","null"]},"mediaId":{"type":"integer"},"position":{"type":"integer"},"role":{"type":"string"},"altText":{"type":["string","null"]},"cropRect":{"type":["object","null"],"additionalProperties":true},"derivedKey":{"type":["string","null"]},"url":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"mediaKind":{"type":["string","null"]}}}}}},"scheduleVersion":{"type":"integer"}}}}}},"404":{"description":"`post_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`post_publishing`, `post_not_scheduled` or `schedule_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/targets/{targetId}/remote":{"delete":{"tags":["Social"],"summary":"Delete published post on the network","description":"Deletes the live post of one target on the social network, then cancels the target. Only for networks that support deletion via API.","operationId":"deleteSocialTargetRemote","x-required-scope":"social:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"targetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Deleted on the network","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"targetId":{"type":"integer"},"externalPostId":{"type":["string","null"]},"target":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}}}}}}},"404":{"description":"`post_not_found`, `target_not_found` or `account_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`target_not_published` or `delete_not_supported`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"`remote_delete_failed` (provider error in the message)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/social/posts/{id}/targets/{targetId}/retry":{"post":{"tags":["Social"],"summary":"Retry failed target","description":"Queues a failed target for another publish attempt (background job).","operationId":"retrySocialTarget","x-required-scope":"social:publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"targetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"object","properties":{"id":{"type":"integer"},"postId":{"type":"integer"},"accountId":{"type":"integer"},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"text":{"type":["string","null"]},"options":{"type":["object","null"],"additionalProperties":true},"status":{"type":"string"},"phase":{"type":["string","null"]},"externalPostId":{"type":["string","null"]},"externalUrl":{"type":["string","null"]},"attempts":{"type":"integer"},"lastError":{"type":["string","null"]},"lastErrorCode":{"type":["string","null"]},"errorCategory":{"type":["string","null"]},"nextAttemptAt":{"type":["integer","null"],"description":"Unix seconds"},"publishedAt":{"type":["integer","null"],"description":"Unix seconds"},"canDeleteRemote":{"type":"boolean"},"account":{"type":["object","null"],"properties":{"id":{"type":"integer"},"displayName":{"type":["string","null"]},"handle":{"type":["string","null"]},"avatarUrl":{"type":["string","null"]},"network":{"type":"string","enum":["facebook_page","instagram","threads","linkedin_profile","linkedin_page","x","tiktok","youtube","pinterest","bluesky","mastodon","google_business"]},"status":{"type":"string"},"publishingChannelId":{"type":["integer","null"]}}}}},"attempt":{"type":"integer"},"accepted":{"type":"boolean"}}}}}},"402":{"description":"Plan upgrade required","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"`post_not_found` or `target_not_found`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"`reconnect_required`, `target_not_retryable` or `retry_conflict`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/approval":{"get":{"tags":["Orders"],"summary":"Approval context","description":"Recipients, contacts, signature and article titles for sending editorial approval requests for all articles of an order.","operationId":"getOrderApprovalContext","x-required-scope":"orders:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Approval context","content":{"application/json":{"schema":{"type":"object","properties":{"customerName":{"type":"string"},"customerOrganizationId":{"type":["string","null"]},"mode":{"type":"string"},"emailContacts":{"type":"array","items":{"type":"object","additionalProperties":true}},"signature":{"type":"string"},"articleTitles":{"type":"array","items":{"type":"string"}},"recipients":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","format":"email"}}}},"approvalStatus":{"type":["string","null"]},"requiresApproval":{"type":"boolean"},"articleCount":{"type":"integer"},"recipientArticleTitles":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"senderSignature":{"type":"string"}}}}}},"403":{"description":"`customer_forbidden`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`order_not_found` or `content_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`approval_content_required` or `customer_mismatch`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}},"post":{"tags":["Orders"],"summary":"Send approval requests","description":"Creates approval tokens and sends editorial approval emails for all articles of the order. Status 502 carries the same body with `success: false` when at least one email failed.","operationId":"sendOrderApprovalRequests","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["emailPresentation"],"properties":{"recipientEmail":{"type":"string","format":"email"},"message":{"type":"string","maxLength":5000},"emailPresentation":{"type":"object","required":["signature","contacts"],"properties":{"signature":{"type":"string","minLength":1,"maxLength":10000},"contacts":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"object","required":["email","formality","salutation","title","firstName","lastName"],"properties":{"email":{"type":"string","format":"email"},"formality":{"type":"string","enum":["neutral","formal","informal"]},"salutation":{"type":"string","enum":["","Herr","Frau"]},"title":{"type":"string","description":"May be empty"},"firstName":{"type":"string","description":"May be empty"},"lastName":{"type":"string","description":"May be empty"}}}}}}}}}}},"responses":{"200":{"description":"All emails sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"articleCount":{"type":"integer"},"emailResults":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email"},"sent":{"type":"boolean"}}}}}}}}},"400":{"description":"`invalid_request`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`customer_forbidden`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`order_not_found` or `content_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`recurring_program_content_guard` or `content_remote_mutation_conflict`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`approval_content_required` (order closed or articles without content), `approval_recipients_insufficient`, `approval_email_required` or `approval_email_details_required`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"At least one email failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"articleCount":{"type":"integer"},"emailResults":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email"},"sent":{"type":"boolean"}}}}}}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}},"/api/orders/{id}/line-items/sync-channels":{"post":{"tags":["Orders"],"summary":"Sync channel line items","description":"Rebuilds the order's channel line items from the linked campaign's articles in one transaction and recomputes the totals. Idempotent.","operationId":"syncOrderChannelLineItems","x-required-scope":"orders:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Synced","content":{"application/json":{"schema":{"type":"object","properties":{"synced":{"type":"integer"},"totals":{"type":["object","null"],"properties":{"taxExemptionReason":{"type":"string"},"isTaxExempt":{"type":"boolean"},"isReverseCharge":{"type":"boolean"},"netTotal":{"type":"integer","description":"Cents"},"taxTotal":{"type":"integer","description":"Cents"},"grossTotal":{"type":"integer","description":"Cents"},"lines":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"400":{"description":"Invalid order or campaign ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Order or campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Pricing locked (invoice exists or order closed) or concurrent modification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No linked campaign or campaign link rule violated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OrgResolutionFailed"}}}}},"components":{"responses":{"OrgResolutionFailed":{"description":"The organization context of the API key could not be verified (temporary database error). The request fails closed; retry later. `code`: `org_resolution_failed`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["org_resolution_failed"]}},"required":["error","code"]}}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key as Bearer token: `Authorization: Bearer cmm_your_key`"},"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key via custom header: `X-API-Key: cmm_your_key`"},"ApiKeyQuery":{"type":"apiKey","in":"query","name":"api_key","description":"API key as query parameter (for simple testing only)"},"SessionCookie":{"type":"apiKey","in":"cookie","name":"__Secure-authjs.session-token","description":"Dashboard browser session (local: authjs.session-token). Not for integrations; API keys receive 401 here."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"string","description":"Additional error details"}},"required":["error"]},"ErrorResponse":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","enum":["invalid_input","unknown_field","guarded_pilot_invalid","site_not_owned","group_empty","model_unavailable","model_catalog_unavailable","forbidden_permission","group_not_owned","conflict_stale","one_shot_active","program_managed","not_found","draft_upload_target_invalid","forbidden_scope","category_invalid","roadmap_topic_not_planned","roadmap_cycle_in_progress","roadmap_category_conflict","org_mismatch","org_membership_revoked","org_resolution_failed","internal_error","customer_org_not_owned","primary_contact_requires_organization","primary_contact_not_member","primary_contact_not_accepted"]},"details":{"description":"Optional structured details (validation issues, `field`)."}}},"CampaignLink":{"type":"object","additionalProperties":false,"required":["keyword","url"],"properties":{"keyword":{"type":"string","minLength":1,"maxLength":100,"description":"Anchor text."},"url":{"type":"string","maxLength":2048,"description":"Target URL: http or https, no credentials."},"linkType":{"type":"string","enum":["dofollow","nofollow","sponsored"],"default":"dofollow"}}},"CampaignLinks":{"type":"object","additionalProperties":false,"required":["links","updatedAt","maxLinks"],"properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/CampaignLink"}},"updatedAt":{"type":["integer","null"],"description":"Campaign `updatedAt` (epoch seconds), usable as `expectedUpdatedAt`."},"maxLinks":{"type":"integer","description":"Largest list a PUT accepts."}}},"CampaignLinksUpdate":{"type":"object","additionalProperties":false,"required":["links"],"properties":{"links":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/CampaignLink"}},"expectedUpdatedAt":{"type":"integer","minimum":0,"description":"Optimistic concurrency token; a mismatch returns 409 `conflict_stale`."}}},"RoadmapTopic":{"type":"object","additionalProperties":false,"required":["id","topic","keywords","scheduledDate","wordPressCategoryId","siteBound","status","position","eligible","blockedReason","siteFitReason","createdAt","updatedAt"],"properties":{"id":{"type":"integer","description":"Article id (`articleId` in the path)."},"topic":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"scheduledDate":{"type":["string","null"],"description":"`YYYY-MM-DD`; ordering hint only."},"wordPressCategoryId":{"type":["integer","null"]},"siteBound":{"type":"boolean","description":"A site or channel is set; such a topic is edited in the dashboard."},"status":{"type":"string"},"position":{"type":["integer","null"],"description":"1-based position in the generator order; null beyond the list limit."},"eligible":{"type":"boolean","description":"The generator may draw this topic (worker parity)."},"blockedReason":{"type":["string","null"],"enum":["locked","content_in_progress","already_generated_topic","missing_category","site_fit",null]},"siteFitReason":{"type":["string","null"],"enum":["campaign_language_missing","language_mismatch","excluded_topic","editorial_site",null],"description":"Only with `blockedReason = site_fit`."},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer","description":"Usable as `expectedUpdatedAt`."}}},"RoadmapTopics":{"type":"object","additionalProperties":false,"required":["topics","truncated"],"properties":{"topics":{"type":"array","items":{"$ref":"#/components/schemas/RoadmapTopic"}},"truncated":{"type":"boolean","description":"More than 500 open topics exist."}}},"RoadmapTopicInput":{"type":"object","additionalProperties":false,"required":["topic"],"properties":{"topic":{"type":"string","minLength":1,"maxLength":200},"keywords":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":100}},"scheduledDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"wordPressCategoryId":{"type":["integer","null"],"minimum":1}}},"RoadmapAdd":{"type":"object","additionalProperties":false,"required":["topics"],"properties":{"topics":{"type":"array","minItems":1,"maxItems":52,"items":{"$ref":"#/components/schemas/RoadmapTopicInput"}}}},"RoadmapAddResult":{"type":"object","additionalProperties":false,"required":["created","skipped"],"properties":{"created":{"type":"array","items":{"$ref":"#/components/schemas/RoadmapTopic"}},"skipped":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["index","topic","reason"],"properties":{"index":{"type":"integer","description":"Index in the request."},"topic":{"type":"string"},"reason":{"type":"string","enum":["duplicate_topic"]}}}}}},"RoadmapPatch":{"type":"object","additionalProperties":false,"minProperties":1,"anyOf":[{"required":["topic"]},{"required":["keywords"]},{"required":["scheduledDate"]},{"required":["wordPressCategoryId"]}],"properties":{"topic":{"type":"string","minLength":1,"maxLength":200},"keywords":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":100}},"scheduledDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"wordPressCategoryId":{"type":["integer","null"],"minimum":1},"expectedUpdatedAt":{"type":"integer","minimum":0,"description":"Topic `updatedAt`; a mismatch returns 409 `conflict_stale`."}}},"RecurringChannelFilter":{"type":"object","additionalProperties":false,"description":"Allow-listed view of the target selection. Server-owned fields are never returned.","properties":{"mode":{"type":"string","enum":["all","list","group"]},"siteIds":{"type":["array","null"],"items":{"type":"integer"}},"groupId":{"type":["integer","null"]},"rotate":{"type":["boolean","null"]},"timeZone":{"type":"string","description":"IANA time zone for time of day and day boundary"},"publicationMode":{"type":"string","enum":["auto","draft"]},"generationModels":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string"},"image":{"type":"string"}}},"contentLength":{"type":"string","enum":["short","medium","long","very-long"]},"weeklySlotGuard":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"timeZone":{"type":"string"},"minimumWordCount":{"type":"integer"},"requireFeaturedMedia":{"type":"boolean"}}}}},"RecurringConfig":{"type":"object","properties":{"campaignId":{"type":["integer","null"]},"isRecurring":{"type":"boolean"},"recurrencePattern":{"type":["string","null"],"enum":["daily","weekly","monthly",null]},"recurrenceDay":{"type":["integer","null"],"description":"weekly: 0-6 (0=Sunday), monthly: 1-28"},"recurrenceTime":{"type":["string","null"],"description":"HH:MM as stored; may be null"},"maxArticles":{"type":["integer","null"]},"pauseOnHolidays":{"type":"boolean"},"recurringContext":{"type":["string","null"]},"nextGenerationAt":{"type":["integer","null"],"description":"Unix seconds, server-maintained (read-only)"},"generatedTopicsCount":{"type":"integer"},"updatedAt":{"type":["integer","null"],"description":"Unix seconds; use as `expectedUpdatedAt`"},"channelFilter":{"$ref":"#/components/schemas/RecurringChannelFilter"},"schedule":{"type":"object","properties":{"effectiveTime":{"type":["string","null"],"description":"Time of day used for the schedule (09:00 if none stored)"},"timeZone":{"type":"string"},"timeZoneSource":{"type":"string","enum":["campaign","server"]}}},"effectiveGenerationModels":{"type":"object","properties":{"text":{"type":"string"},"image":{"type":"string"},"imageQuality":{"type":"string"},"source":{"type":"string","enum":["campaign","default"]}}}}},"RecurringConfigPatch":{"type":"object","additionalProperties":false,"description":"Partial update. `isRecurring` is not accepted (use activate/pause). Unknown fields return 400 `unknown_field`.","properties":{"recurrencePattern":{"type":"string","enum":["daily","weekly","monthly"]},"recurrenceDay":{"type":["integer","null"],"minimum":0,"maximum":28},"recurrenceTime":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"HH:MM"},"maxArticles":{"type":["integer","null"],"minimum":1,"maximum":10000},"pauseOnHolidays":{"type":"boolean"},"recurringContext":{"type":["string","null"],"maxLength":10000},"channelFilter":{"type":"object","additionalProperties":false,"description":"Deep-merged over the stored value. `null` clears publicationMode, generationModels and contentLength.","properties":{"mode":{"type":"string","enum":["all","list","group"]},"publicationMode":{"type":["string","null"],"enum":["auto","draft",null]},"generationModels":{"type":["object","null"],"additionalProperties":false,"required":["text","image"],"properties":{"text":{"type":"string"},"image":{"type":"string"}}},"contentLength":{"type":["string","null"],"enum":["short","medium","long","very-long",null]},"timeZone":{"type":"string","description":"IANA time zone"},"siteIds":{"type":["array","null"],"items":{"type":"integer","minimum":1},"maxItems":500},"groupId":{"type":["integer","null"],"minimum":1},"rotate":{"type":["boolean","null"]},"weeklySlotGuard":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean"},"timeZone":{"type":"string"},"minimumWordCount":{"type":"integer","minimum":0,"maximum":10000},"requireFeaturedMedia":{"type":"boolean"}}}}},"expectedUpdatedAt":{"type":"integer","minimum":0,"description":"Optimistic concurrency: 409 `conflict_stale` if the stored `updatedAt` differs."}}},"RecurringStateChange":{"type":"object","additionalProperties":false,"properties":{"expectedUpdatedAt":{"type":"integer","minimum":0,"description":"Optimistic concurrency (optional)."}}},"CampaignCostBreakdown":{"type":"object","properties":{"aiText":{"type":"number"},"aiImage":{"type":"number"},"marketplace":{"type":"number"},"total":{"type":"number"},"unit":{"type":"string","enum":["cent"]},"currency":{"type":"string","enum":["EUR"]}}},"RecurringStatus":{"type":"object","properties":{"campaignId":{"type":"integer"},"state":{"type":"string","enum":["not_configured","active","paused","cancelled","completed"]},"isRecurring":{"type":"boolean"},"updatedAt":{"type":["integer","null"]},"nextRun":{"type":["object","null"],"properties":{"at":{"type":"integer","description":"Unix seconds"},"iso":{"type":"string"},"timeZone":{"type":"string"},"timeZoneSource":{"type":"string","enum":["campaign","server"]},"local":{"type":"string","description":"YYYY-MM-DD HH:MM in `timeZone`"},"due":{"type":"boolean"}}},"counters":{"type":"object","properties":{"generated":{"type":"integer"},"published":{"type":"integer"},"plannedRemaining":{"type":"integer"},"maxArticles":{"type":["integer","null"]},"remaining":{"type":["integer","null"]},"maxArticlesReached":{"type":"boolean"}}},"lastSuccessfulRun":{"type":["object","null"],"properties":{"at":{"type":"integer"},"iso":{"type":"string"},"campaignArticleId":{"type":"integer"},"contentId":{"type":"integer"},"contentStatus":{"type":["string","null"]},"wordPressSiteId":{"type":["integer","null"]},"source":{"type":"string","enum":["derived"]}}},"lastRun":{"oneOf":[{"$ref":"#/components/schemas/RecurringRun"},{"type":"null"}],"description":"Newest entry of the run history (any outcome). Null for campaigns without runs, e.g. programme campaigns, which the run history does not cover."},"pause":{"allOf":[{"$ref":"#/components/schemas/RecurringStopEvent"}],"description":"Most recent automatic pause while the campaign is paused. Taken from the run history (`source: run`) when the newest run paused the campaign; otherwise from the owner notification (`source: notification`, heuristic: manual pauses create no notification, and the event is dropped if a run happened after it)."},"lastStopEvent":{"$ref":"#/components/schemas/RecurringStopEvent"},"pilotGuard":{"type":"object","properties":{"applies":{"type":"boolean"},"valid":{"type":"boolean"},"error":{"type":["string","null"]}}},"costs":{"oneOf":[{"$ref":"#/components/schemas/CampaignCostBreakdown"},{"type":"null"}]}}},"RecurringStopEvent":{"type":["object","null"],"description":"Most recent pause/skip event from the run history or the owner notification (code only, never its text).","properties":{"reasonCode":{"type":"string"},"label":{"type":["string","null"]},"kind":{"type":"string","enum":["paused","skipped"]},"at":{"type":"integer"},"iso":{"type":"string"},"source":{"type":"string","enum":["notification","run"]}}},"RecurringRun":{"type":"object","description":"One claimed cycle of the recurring generator. `generated` means the article generation was dispatched; if it later fails, the run becomes `failed`. A run without a result after 2 hours becomes `stale`.","properties":{"id":{"type":"integer"},"cycleAt":{"type":"integer","description":"Claimed cycle (Unix seconds); unique per campaign"},"cycleIso":{"type":"string"},"startedAt":{"type":"integer"},"startedIso":{"type":"string"},"finishedAt":{"type":["integer","null"]},"finishedIso":{"type":["string","null"]},"outcome":{"type":"string","enum":["running","generated","skipped","paused","failed","stale"]},"reasonCode":{"type":["string","null"],"description":"Stop reason code, as in the status (`slot_occupied`, `insufficient_credits`, ...). `cycle_merged` (outcome `skipped`): an overdue occurrence merged into an earlier run instead of being caught up (#1209)"},"label":{"type":["string","null"]},"detail":{"type":["string","null"],"description":"Short diagnostic text (max. 500 characters, URLs without query)"},"campaignArticleId":{"type":["integer","null"]},"contentId":{"type":["integer","null"]},"wordPressSiteId":{"type":["integer","null"]},"creditsCharged":{"type":"integer"},"source":{"type":"string","enum":["run"]}}},"RecurringRunsPage":{"type":"object","properties":{"campaignId":{"type":"integer"},"runs":{"type":"array","items":{"$ref":"#/components/schemas/RecurringRun"}},"nextBefore":{"type":["integer","null"],"description":"Pass as `before` to fetch the next (older) page; null on the last page."}}},"PaginatedResponse":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of items"},"limit":{"type":"integer","description":"Items per page"},"offset":{"type":"integer","description":"Number of items skipped"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"keyPrefix":{"type":"string","description":"First 8 chars of the key (e.g. cmm_abc1)"},"permissions":{"type":"array","items":{"type":"string"},"description":"Scopes like content:read, campaigns:write"},"lastUsedAt":{"type":"integer","nullable":true,"description":"Unix timestamp"},"expiresAt":{"type":"integer","nullable":true,"description":"Unix timestamp"},"isActive":{"type":"boolean"},"createdAt":{"type":"integer","description":"Unix timestamp"},"revokedAt":{"type":"integer","nullable":true,"description":"Unix timestamp"}}},"ApiKeyWithPlainKey":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"plainKey":{"type":"string","description":"The full API key — shown only once at creation time"}}}]},"Content":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"content":{"type":"string","nullable":true,"description":"HTML body"},"language":{"type":"string","description":"ISO language code (e.g. de, en)"},"prompt":{"type":"string","nullable":true},"contentLength":{"type":"string","enum":["short","medium","long"]},"keywords":{"type":"array","items":{"type":"string"},"nullable":true},"metadata":{"type":"object","nullable":true},"isDraft":{"type":"boolean"},"status":{"type":"string","enum":["draft","generating","review","approved","scheduled","publishing","published","failed"]},"assignedTo":{"type":"string","nullable":true},"authorProfileId":{"type":"string","format":"uuid","nullable":true},"authorName":{"type":"string","nullable":true},"scheduledAt":{"type":"integer","nullable":true,"description":"Unix timestamp"},"publishedAt":{"type":"integer","nullable":true,"description":"Unix timestamp"},"wordPressPostId":{"type":"integer","nullable":true},"wordPressUrl":{"type":"string","nullable":true},"featuredImagePrompt":{"type":"string","nullable":true},"featuredImageUrl":{"type":"string","nullable":true},"wordPressSiteId":{"type":"integer","nullable":true},"categoryId":{"type":"integer","nullable":true},"userId":{"type":"string"},"approvalStatus":{"type":"string","nullable":true},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"},"siteName":{"type":"string","nullable":true},"siteUrl":{"type":"string","nullable":true},"categoryName":{"type":"string","nullable":true}}},"AuthorProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"orgId":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"headline":{"type":"string","nullable":true},"shortBio":{"type":"string"},"longBio":{"type":"string","nullable":true},"imageUrl":{"type":"string","format":"uri","nullable":true},"socialLinks":{"type":"object","additionalProperties":{"type":"string"}},"credentials":{"type":"array","items":{"type":"object"}},"experience":{"type":"array","items":{"type":"object"}},"publications":{"type":"array","items":{"type":"object"}},"certifications":{"type":"array","items":{"type":"object"}},"template":{"type":"string","enum":["minimal","card"]},"isActive":{"type":"boolean"},"archivedAt":{"type":"integer","nullable":true},"targets":{"type":"array","items":{"type":"object"}},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}}},"WordPressSite":{"type":"object","properties":{"id":{"type":"integer"},"userId":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"hasApiToken":{"type":"boolean","description":"Whether a write-only WordPress API credential is configured"},"status":{"type":"string"},"description":{"type":"string","nullable":true},"editorialLine":{"type":"string","nullable":true},"excludedTopics":{"type":"string","nullable":true},"allowedLanguages":{"type":"array","items":{"type":"string"},"nullable":true},"primaryLanguage":{"type":"string","nullable":true},"maxPostsPerDay":{"type":"integer","nullable":true,"description":"Daily publication limit (Europe/Vienna calendar day); null = unlimited"},"lastSyncedAt":{"type":"integer","nullable":true,"description":"Unix timestamp"},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"SiteDailyCapacity":{"type":"object","description":"API-key view of `GET /api/wordpress-sites/daily-capacity` (the session receives `{ warning }`).","properties":{"siteId":{"type":"integer"},"day":{"type":"string","description":"YYYY-MM-DD, Europe/Vienna"},"maxPostsPerDay":{"type":["integer","null"]},"occupied":{"type":["integer","null"],"description":"Posts and reservations counted today; null without a limit"},"remaining":{"type":["integer","null"]},"full":{"type":"boolean"}}},"WordPressSiteListItem":{"allOf":[{"$ref":"#/components/schemas/WordPressSite"},{"type":"object","required":["hasApiToken"],"properties":{"hasApiToken":{"type":"boolean","description":"Whether a write-only WordPress API credential is configured"}}}]},"Campaign":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"customer":{"type":"string"},"topic":{"type":"string"},"targetAudience":{"type":"string","nullable":true},"totalBudget":{"type":"number","nullable":true},"status":{"type":"string","enum":["draft","planned","active","paused","completed","cancelled","trash"]},"startDate":{"type":"string","nullable":true},"endDate":{"type":"string","nullable":true},"language":{"type":"string"},"summary":{"type":"string","nullable":true},"userId":{"type":"string"},"customerOrganizationId":{"type":"integer","nullable":true},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"CampaignArticle":{"type":"object","properties":{"id":{"type":"integer"},"campaignId":{"type":"integer"},"title":{"type":"string"},"topic":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"},"nullable":true},"siteName":{"type":"string"},"channelId":{"type":"integer","nullable":true},"wordPressSiteId":{"type":"integer","nullable":true},"wordPressCategoryId":{"type":"integer","nullable":true},"isOwnSite":{"type":"boolean"},"estimatedCost":{"type":"integer","nullable":true,"description":"Cent (EUR), #1160"},"scheduledDate":{"type":"string","nullable":true},"contentId":{"type":"integer","nullable":true},"status":{"type":"string"}}},"ChannelCatalog":{"type":"object","additionalProperties":false,"description":"Public sales information only; never supplier contacts or raw legacy metadata.","properties":{"screenshotUrl":{"type":"string","format":"uri","nullable":true},"countries":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"sistrixVisibility":{"type":"number","minimum":0,"nullable":true},"textingIncluded":{"type":"boolean","nullable":true},"includedServices":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"ChannelSupplier":{"type":"object","additionalProperties":false,"description":"Private to the offering account owner/admin. No global-admin bypass.","properties":{"company":{"type":"string","maxLength":500},"name":{"type":"string","maxLength":500},"email":{"type":"string","maxLength":1000},"phone":{"type":"string","maxLength":500},"notes":{"type":"string","maxLength":20000}}},"PublicCampaign":{"type":"object","additionalProperties":false,"description":"Allow-listed campaign view for API keys. `updatedAt` is the value for `expectedUpdatedAt`; it only detects changes made in a later second than the one you read (second granularity). The list items use `PublicCampaignListItem`.","properties":{"id":{"type":"integer"},"name":{"type":"string"},"status":{"type":"string"},"totalBudget":{"type":["integer","null"]},"actualCosts":{"type":"number","description":"Cents"},"costBreakdown":{"$ref":"#/components/schemas/CampaignCostBreakdown"},"createdAt":{"type":"integer","description":"Unix timestamp (seconds)"},"updatedAt":{"type":"integer","description":"Unix timestamp (seconds); use as expectedUpdatedAt"},"userId":{"type":"string"},"customer":{"type":"string"},"customerEmail":{"type":["string","null"]},"customerOrganizationId":{"type":["integer","null"]},"primaryContactUserId":{"type":["string","null"]},"campaignApprovalMode":{"type":["string","null"],"enum":["none","one","all","custom",null]},"campaignApprovalMinCount":{"type":["integer","null"]},"primaryContactEmail":{"type":["string","null"]},"primaryContactName":{"type":["string","null"]},"topic":{"type":"string"},"targetAudience":{"type":["string","null"]},"summary":{"type":["string","null"]},"language":{"type":"string"},"startDate":{"type":["string","null"]},"endDate":{"type":["string","null"]},"includeOwnSites":{"type":["boolean","null"]},"includeMarketplace":{"type":["boolean","null"]},"isRecurring":{"type":["boolean","null"]},"recurrencePattern":{"type":["string","null"]},"recurrenceDay":{"type":["integer","null"]},"recurrenceTime":{"type":["string","null"]},"nextGenerationAt":{"type":["integer","null"]},"maxArticles":{"type":["integer","null"]},"pauseOnHolidays":{"type":["boolean","null"]},"recurringContext":{"type":["string","null"]},"generatedTopics":{"type":["array","null"],"items":{"type":"string"}},"channelFilter":{"$ref":"#/components/schemas/RecurringChannelFilter"}}},"PublicCampaignListItem":{"type":"object","additionalProperties":false,"description":"Campaign as returned in GET /api/campaigns for API keys.","properties":{"id":{"type":"integer"},"name":{"type":"string"},"status":{"type":"string"},"totalBudget":{"type":["integer","null"]},"actualCosts":{"type":"number","description":"Cents"},"costBreakdown":{"$ref":"#/components/schemas/CampaignCostBreakdown"},"createdAt":{"type":"integer","description":"Unix timestamp (seconds)"},"updatedAt":{"type":"integer","description":"Unix timestamp (seconds); use as expectedUpdatedAt"},"userId":{"type":"string"},"customer":{"type":"string"},"customerEmail":{"type":["string","null"]},"customerOrganizationId":{"type":["integer","null"]},"primaryContactUserId":{"type":["string","null"]},"campaignApprovalMode":{"type":["string","null"],"enum":["none","one","all","custom",null]},"campaignApprovalMinCount":{"type":["integer","null"]},"primaryContactEmail":{"type":["string","null"]},"primaryContactName":{"type":["string","null"]},"topic":{"type":"string"},"targetAudience":{"type":["string","null"]},"summary":{"type":["string","null"]},"language":{"type":"string"},"startDate":{"type":["string","null"]},"endDate":{"type":["string","null"]},"includeOwnSites":{"type":["boolean","null"]},"includeMarketplace":{"type":["boolean","null"]},"isRecurring":{"type":["boolean","null"]},"recurrencePattern":{"type":["string","null"]},"recurrenceDay":{"type":["integer","null"]},"recurrenceTime":{"type":["string","null"]},"nextGenerationAt":{"type":["integer","null"]},"maxArticles":{"type":["integer","null"]},"pauseOnHolidays":{"type":["boolean","null"]},"recurringContext":{"type":["string","null"]},"generatedTopics":{"type":["array","null"],"items":{"type":"string"}},"channelFilter":{"$ref":"#/components/schemas/RecurringChannelFilter"},"messageCount":{"type":"integer"},"unreadMessageCount":{"type":"integer"},"articles":{"type":"array","items":{"$ref":"#/components/schemas/PublicCampaignArticle"}}}},"PublicCampaignArticle":{"type":"object","additionalProperties":false,"description":"Allow-listed article view for API keys. Approval tokens and publisher internals are never returned. `imageSource`, `ciStatus`, `ciExperienceReviewState`, `ciPublishedAt` and `ciScheduledAt` appear only in GET /api/campaigns.","properties":{"id":{"type":"integer"},"campaignId":{"type":"integer"},"channelId":{"type":["integer","null"]},"wordPressSiteId":{"type":["integer","null"]},"wordPressCategoryId":{"type":["integer","null"]},"isOwnSite":{"type":["boolean","null"]},"siteName":{"type":"string"},"title":{"type":"string"},"topic":{"type":"string"},"keywords":{"type":["array","null"],"items":{"type":"string"}},"scheduledDate":{"type":["string","null"]},"seriesId":{"type":["integer","null"]},"seriesOrder":{"type":["integer","null"]},"estimatedCost":{"type":"integer","description":"Cent (EUR), #1160"},"status":{"type":"string"},"contentId":{"type":["integer","null"]},"customerApproved":{"type":["boolean","null"]},"customerApprovedAt":{"type":["integer","null"]},"customerFeedback":{"type":["string","null"]},"suggestedTitle":{"type":["string","null"]},"revisionRequested":{"type":["boolean","null"]},"revisionCompletedAt":{"type":["integer","null"]},"shortSummary":{"type":["string","null"]},"imagePrompt":{"type":["string","null"]},"imagePreviewUrl":{"type":["string","null"]},"targetLinkUrl":{"type":["string","null"]},"bookingId":{"type":["integer","null"]},"autoApproveEligible":{"type":["boolean","null"]},"seoMainKeyword":{"type":["string","null"]},"seoSecondaryKeywords":{"type":["array","null"],"items":{"type":"string"}},"keywordInH1":{"type":["boolean","null"]},"keywordInSlug":{"type":["boolean","null"]},"keywordInMeta":{"type":["boolean","null"]},"metaDescriptionHint":{"type":["string","null"]},"contentLengthPreset":{"type":["string","null"]},"wordCountMin":{"type":["integer","null"]},"wordCountMax":{"type":["integer","null"]},"faqEnabled":{"type":["boolean","null"]},"faqCount":{"type":["integer","null"]},"faqTopics":{"type":["array","null"],"items":{"type":"string"}},"faqSchemaEnabled":{"type":["boolean","null"]},"generatedFaqs":{"type":["array","null"],"items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":"string"}}}},"keywordLinks":{"type":["array","null"],"items":{"type":"object","properties":{"keyword":{"type":"string"},"url":{"type":"string"},"linkType":{"type":"string","enum":["dofollow","nofollow","sponsored"]}}}},"isLocked":{"type":["boolean","null"]},"generatedTitle":{"type":["string","null"]},"generatedContent":{"type":["string","null"]},"contentStatus":{"type":["string","null"]},"generatedMetaTitle":{"type":["string","null"]},"generatedMetaDescription":{"type":["string","null"]},"generatedKeywords":{"type":["array","null"],"items":{"type":"string"}},"categoryNameAtGeneration":{"type":["string","null"]},"createdAt":{"type":"integer","description":"Unix timestamp (seconds)"},"updatedAt":{"type":"integer","description":"Unix timestamp (seconds)"},"generatedImageUrl":{"type":["string","null"]},"publishedUrl":{"type":["string","null"]},"imageSource":{"type":["string","null"],"description":"Only in GET /api/campaigns"},"ciStatus":{"type":["string","null"],"description":"Only in GET /api/campaigns"},"ciExperienceReviewState":{"type":["string","null"],"description":"Only in GET /api/campaigns"},"ciPublishedAt":{"type":["integer","null"],"description":"Only in GET /api/campaigns"},"ciScheduledAt":{"type":["integer","null"],"description":"Only in GET /api/campaigns"}}},"CampaignUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1},"customer":{"type":"string","minLength":1},"customerOrganizationId":{"type":["integer","null"],"minimum":1,"maximum":2147483647},"primaryContactUserId":{"type":["string","null"]},"campaignApprovalMode":{"type":["string","null"],"enum":["none","one","all","custom",null]},"campaignApprovalMinCount":{"type":["integer","null"],"minimum":1,"maximum":2147483647},"topic":{"type":"string","minLength":1},"targetAudience":{"type":["string","null"]},"totalBudget":{"type":"integer","minimum":0,"maximum":2147483647},"startDate":{"type":["string","null"]},"endDate":{"type":["string","null"]},"language":{"type":"string","minLength":1},"includeOwnSites":{"type":"boolean"},"includeMarketplace":{"type":"boolean"},"expectedUpdatedAt":{"type":"integer","minimum":0,"description":"Optimistic concurrency (optional): a stale value returns 409 conflict_stale."}}},"ChannelListing":{"type":"object","properties":{"catalog":{"$ref":"#/components/schemas/ChannelCatalog"},"isExternal":{"type":"boolean"},"publisherBasePrice":{"type":"integer","nullable":true,"description":"Purchase price in cents. Only for the listing owner/owning org or a global admin, and always null in view=marketplace."},"platformFeePercentage":{"type":"string","nullable":true,"description":"Platform fee in percent. Same visibility as publisherBasePrice (it can reveal the purchase price)."},"id":{"type":"integer"},"wordPressSiteId":{"type":"integer"},"pricePerBooking":{"type":"string"},"isAvailable":{"type":"boolean"},"description":{"type":"string","nullable":true},"categoryId":{"type":"integer","nullable":true},"editorialLine":{"type":"string","nullable":true},"excludedTopics":{"type":"string","nullable":true},"monthlyVisitors":{"type":"integer","nullable":true},"linkType":{"type":"string","enum":["dofollow","nofollow","mixed","sponsored"],"nullable":true},"bookingDurationDays":{"type":"integer","nullable":true},"sponsoredLabelRequired":{"type":"boolean"},"siteName":{"type":"string"},"siteUrl":{"type":"string"},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"Booking":{"type":"object","properties":{"id":{"type":"integer"},"channelListingId":{"type":"integer"},"contentTitle":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","rejected","in_progress","content_submitted","revision_requested","published","completed","cancelled"]},"scheduledDate":{"type":"integer","nullable":true,"description":"Unix timestamp"},"notes":{"type":"string","nullable":true},"publishedUrl":{"type":"string","nullable":true},"totalCost":{"type":"string","description":"Price the advertiser pays, in cents"},"platformFeeAmount":{"type":"string","nullable":true,"description":"Cents. Only for the publisher side and global admins; null for the advertiser."},"publisherPayoutAmount":{"type":"string","nullable":true,"description":"Cents. Only for the publisher side and global admins; null for the advertiser."},"advertiserId":{"type":"string"},"publisherId":{"type":"string"},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"Order":{"type":"object","properties":{"id":{"type":"integer"},"publicId":{"type":"string","nullable":true,"description":"Visible order number, e.g. \"ORD-7K2F9QX4M8A7\""},"customerOrganizationId":{"type":"integer"},"status":{"type":"string","enum":["draft","in_progress","pending_approval","revision_requested","approved","published","invoiced","paid","completed","cancelled"]},"agreedPrice":{"type":"integer","nullable":true,"description":"Computed gross total in cents (cache from line items)"},"currency":{"type":"string"},"description":{"type":"string","nullable":true},"externalReference":{"type":"string","nullable":true,"description":"Order-wide external reference"},"notes":{"type":"string","nullable":true},"contentId":{"type":"integer","nullable":true,"description":"Directly linked single post"},"campaignId":{"type":"integer","nullable":true,"description":"Linked campaign (may be an invisible order container)"},"invoiceStatus":{"type":"string","enum":["none","draft","sent","paid","partially_paid","overdue","cancelled","external","unclear"],"description":"\"unclear\" (#619): an ambiguous Claribill invoice create/send result that the strict reconciliation could not uniquely resolve — see POST /orders/{id}/invoice/reconcile."},"claribillInvoiceId":{"type":"string","nullable":true},"claribillInvoiceNumber":{"type":"string","nullable":true},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"OrderLineItem":{"type":"object","properties":{"id":{"type":"integer"},"orderId":{"type":"integer"},"lineType":{"type":"string","enum":["PRODUCT","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL"]},"source":{"type":"string","enum":["channel","external","addon","discount"]},"label":{"type":"string"},"referenceId":{"type":"integer","nullable":true,"description":"For source=channel: campaign article id (or content id for a directly linked post)"},"quantity":{"type":"integer"},"unitAmount":{"type":"integer","description":"Cents (PRODUCT/DISCOUNT_ABSOLUTE) or basis points (DISCOUNT_PERCENT)"},"taxRate":{"type":"integer","description":"Basis points, e.g. 2000 = 20%"},"externalUrl":{"type":"string","nullable":true,"description":"For source=external: URL of the published post"},"externalReference":{"type":"string","nullable":true,"description":"Per-position external reference (appears on the invoice line)"},"internalCost":{"type":"integer","nullable":true,"description":"Internal purchase cost in cents — never on the customer invoice"},"position":{"type":"integer"},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"MediaItem":{"type":"object","properties":{"id":{"type":"integer"},"filename":{"type":"string"},"url":{"type":"string","format":"uri"},"mimeType":{"type":"string"},"fileSize":{"type":"integer"},"altText":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"source":{"type":"string","enum":["upload","ai","wordpress"]},"tags":{"type":"array","items":{"type":"string"},"nullable":true},"userId":{"type":"string"},"createdAt":{"type":"integer","description":"Unix timestamp"}}},"Organization":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"website":{"type":"string","nullable":true,"format":"uri"},"logoUrl":{"type":"string","nullable":true,"format":"uri"},"industry":{"type":"string","nullable":true},"companySize":{"type":"string","enum":["micro","small","medium","large"],"nullable":true},"description":{"type":"string","nullable":true},"targetAudience":{"type":"string","nullable":true},"brandVoice":{"type":"string","nullable":true},"keyTopics":{"type":"array","items":{"type":"string"},"nullable":true},"approvalMode":{"type":"string","enum":["none","one","all","custom"]},"createdAt":{"type":"integer","description":"Unix timestamp"},"updatedAt":{"type":"integer","description":"Unix timestamp"}}},"TeamMember":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"name":{"type":"string","nullable":true},"role":{"type":"string","enum":["author","customer","agency"]},"joinedAt":{"type":"integer","description":"Unix timestamp"}}},"SiteGroupSite":{"type":"object","additionalProperties":false,"required":["id","name","url"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"url":{"type":"string"}}},"SiteGroup":{"type":"object","additionalProperties":false,"description":"Allow-listed site group for API keys (no `userId`, no credentials). `siteIds` and `sites` list the member sites that belong to the key's organization (without an organization: to the key owner) and are not deleted. Use `id` as `channelFilter.groupId` with `channelFilter.mode = \"group\"` in `PATCH /api/campaigns/{id}/recurring`. That endpoint validates the group and its member sites with the same strict tenant rule as the key view described here (#1168): with an organization only its groups and sites, without one only own rows without organization.","required":["id","name","description","orgId","createdAt","updatedAt","siteIds","sites"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":["string","null"]},"orgId":{"type":["string","null"],"description":"Organization the group belongs to (the key's organization)."},"createdAt":{"type":"integer","description":"Unix seconds"},"updatedAt":{"type":"integer","description":"Unix seconds"},"siteIds":{"type":"array","items":{"type":"integer"}},"sites":{"type":"array","items":{"$ref":"#/components/schemas/SiteGroupSite"}}}},"SiteGroupCreate":{"type":"object","additionalProperties":false,"description":"Unknown fields are rejected with 400 `unknown_field`. Duplicate `siteIds` are removed.","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"siteIds":{"type":"array","maxItems":200,"items":{"type":"integer","minimum":1,"maximum":2147483647}}}},"SiteGroupUpdate":{"type":"object","additionalProperties":false,"description":"Partial update. When `siteIds` is present it replaces the members that are visible to the key (sites of the key's tenant), atomically; `[]` empties those. Members outside the tenant (invisible to the key) stay unchanged. Unknown fields are rejected with 400 `unknown_field`.","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"siteIds":{"type":"array","maxItems":200,"items":{"type":"integer","minimum":1,"maximum":2147483647}}}},"AiModel":{"type":"object","additionalProperties":false,"required":["id","displayName","provider","type","supportsReasoning","gatewayId"],"properties":{"id":{"type":"string","description":"Bare model id (`gpt-5.5`)."},"gatewayId":{"type":"string","description":"Gateway catalog id (`openai/gpt-5.5`). THIS is the value accepted by `channelFilter.generationModels.text` / `.image` in `PATCH /api/campaigns/{id}/recurring`; the bare `id` is rejected there with 400 `model_unavailable`."},"displayName":{"type":"string"},"provider":{"type":"string","enum":["openai","anthropic","google"]},"type":{"type":"string","enum":["text","image"]},"supportsReasoning":{"type":"boolean"}}}}}}