{
  "openapi": "3.1.0",
  "info": {
    "title": "Whizz Scribe API",
    "version": "1.0.0",
    "summary": "The content engine that knows your company.",
    "description": "REST API for research-backed, brand-grounded blog article generation. Article creation is asynchronous: POST /v1/articles returns a job; poll GET /v1/jobs/{id} or register a webhook for job.succeeded / job.failed, then fetch the finished article. Every non-2xx response uses the envelope {\"error\": {\"code\", \"message\"}}. All authenticated responses carry x-ratelimit-limit / x-ratelimit-remaining / x-ratelimit-reset headers (per-key fixed 60-second window, default 60 requests/min).",
    "contact": {
      "name": "Whizz Tech support",
      "email": "support@whizztech.ai",
      "url": "https://scribe.whizztech.ai/docs"
    }
  },
  "servers": [
    {
      "url": "https://scribe.whizztech.ai",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Articles",
      "description": "Create, list, retrieve, and publish articles."
    },
    {
      "name": "Jobs",
      "description": "Asynchronous job status and pipeline progress."
    },
    {
      "name": "Brand Profiles",
      "description": "Brand profiles and knowledge-base sources."
    },
    {
      "name": "Usage",
      "description": "Credit balance and daily usage."
    },
    {
      "name": "Webhooks",
      "description": "Webhook endpoint registration."
    }
  ],
  "paths": {
    "/v1/articles": {
      "post": {
        "tags": [
          "Articles"
        ],
        "operationId": "createArticle",
        "summary": "Create an article (async job)",
        "description": "Validates the request, debits credits (standard 10 / deep_research 25, +3 image, +3 derivatives bundle, +5 non-English), and enqueues the generation pipeline. Returns 202 with the job. With an Idempotency-Key header, a repeat submission returns the original job with 200 and replayed: true.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Unique key per logical creation. Replays return the existing job without a second charge."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateArticleRequest"
              },
              "example": {
                "topic": "How B2B SaaS teams actually cut churn with product analytics",
                "target_keyword": "reduce saas churn",
                "brand_profile_id": "bfa6d2a7-4c1e-4f7a-9a2b-91d3f0a6c8e2",
                "mode": "standard",
                "target_words": 1600,
                "derivatives": [
                  "x_thread"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted.",
            "headers": {
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "x-ratelimit-remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "x-ratelimit-reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobAck"
                },
                "example": {
                  "id": "4d1a8f36-9e02-47b8-b7c4-3a92d5e60f18",
                  "object": "job",
                  "type": "article",
                  "status": "queued",
                  "credits_charged": 13,
                  "replayed": false,
                  "links": {
                    "self": "/v1/jobs/4d1a8f36-9e02-47b8-b7c4-3a92d5e60f18"
                  }
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay — the original job for this Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobAck"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "Articles"
        ],
        "operationId": "listArticles",
        "summary": "List articles",
        "description": "Most recent articles for your organization, newest first.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 50
            },
            "description": "Number of articles to return, capped at 50."
          }
        ],
        "responses": {
          "200": {
            "description": "Article summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ArticleSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/articles/{id}": {
      "get": {
        "tags": [
          "Articles"
        ],
        "operationId": "getArticle",
        "summary": "Retrieve an article",
        "description": "The complete generated article: content in Markdown and HTML, citations, SEO metadata, FAQ, JSON-LD, quality breakdown, and derivatives.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "markdown",
                "html"
              ]
            },
            "description": "markdown omits content_html; html omits content_markdown. Omit for both."
          }
        ],
        "responses": {
          "200": {
            "description": "The article.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "Articles"
        ],
        "operationId": "updateArticle",
        "summary": "Update an article",
        "description": "Edit copy or move the article through its review lifecycle. Changing content_markdown re-renders content_html and recomputes word_count, read_minutes, and flesch_score. Free — no credits are charged.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateArticleRequest"
              },
              "example": {
                "status": "approved"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated article.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/articles/{id}/publish": {
      "post": {
        "tags": [
          "Articles"
        ],
        "operationId": "publishArticle",
        "summary": "Publish an article to a connected CMS",
        "description": "Pushes the article through a WordPress, Sanity, or Contentful integration configured in the dashboard. CMS-side failures return 502 publish_failed with the upstream reason.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              },
              "example": {
                "integration_id": "e8d3b6a1-42f7-49c0-95ab-7c614f20d9e8",
                "as_draft": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishResult"
                },
                "example": {
                  "object": "publish",
                  "status": "published",
                  "external_id": "1284",
                  "external_url": "https://www.example.com/blog/saas-churn-product-analytics"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "The downstream CMS rejected the publish.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "code": "publish_failed",
                    "message": "WordPress publish failed (401): rest_cannot_create"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "operationId": "getJob",
        "summary": "Retrieve a job",
        "description": "Job status with live per-stage pipeline progress. On success, result carries the job-type-specific payload (article jobs: article_id, title, word_count, quality_score, citations; ingest jobs: pages, chunks).",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/brand-profiles": {
      "post": {
        "tags": [
          "Brand Profiles"
        ],
        "operationId": "createBrandProfile",
        "summary": "Create a brand profile",
        "description": "With ingest_website: true (default) and a website_url, the site is registered as a knowledge-base source and an ingest job starts immediately; its ID is returned as ingest_job_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBrandProfileRequest"
              },
              "example": {
                "name": "Acme Analytics",
                "website_url": "https://www.acme-analytics.com",
                "description": "Self-serve product analytics for B2B SaaS teams",
                "ingest_website": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandProfileAck"
                },
                "example": {
                  "id": "bfa6d2a7-4c1e-4f7a-9a2b-91d3f0a6c8e2",
                  "object": "brand_profile",
                  "name": "Acme Analytics",
                  "website_url": "https://www.acme-analytics.com",
                  "voice_status": "none",
                  "ingest_job_id": "0c9be9a4-2b77-4e19-8a5d-f21e6b40c7d3"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "Brand Profiles"
        ],
        "operationId": "listBrandProfiles",
        "summary": "List brand profiles",
        "responses": {
          "200": {
            "description": "Brand profiles, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BrandProfileSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/brand-profiles/{id}/sources": {
      "post": {
        "tags": [
          "Brand Profiles"
        ],
        "operationId": "addBrandProfileSource",
        "summary": "Add a knowledge-base source",
        "description": "Registers a source and starts an ingest job (crawl/chunk/contextualize/embed/index, then fact-sheet and voice-profile refresh). url is required for website/url types; content is required for text/writing_sample types.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSourceRequest"
              },
              "example": {
                "type": "writing_sample",
                "title": "Founder blog — pricing teardown",
                "content": "Full text of a post that sounds exactly like the brand…"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Source accepted; ingestion started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceAck"
                },
                "example": {
                  "id": "72e5a1c9-8d40-4b3f-a6e7-05c1d9b82f34",
                  "object": "kb_source",
                  "status": "pending",
                  "ingest_job_id": "5b8f0d21-6a3e-4c97-b1d0-84e7f2a9c655"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "Brand Profiles"
        ],
        "operationId": "listBrandProfileSources",
        "summary": "List a profile's sources",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Sources with ingestion status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KbSource"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "operationId": "getUsage",
        "summary": "Credit balance and daily usage",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30,
              "maximum": 90
            },
            "description": "Reporting window in days, capped at 90."
          }
        ],
        "responses": {
          "200": {
            "description": "Usage report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                },
                "example": {
                  "object": "usage",
                  "days": 7,
                  "credit_balance": 118,
                  "daily": [
                    {
                      "day": "2026-07-01",
                      "credits": 20,
                      "requests": 2,
                      "tokens_in": 184220,
                      "tokens_out": 21458
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookEndpoint",
        "summary": "Register a webhook endpoint",
        "description": "The whsec_ signing secret is returned only in this response. Deliveries follow the Standard Webhooks spec: headers webhook-id, webhook-timestamp, webhook-signature = \"v1,\" + base64(HMAC-SHA256(base64decode(secret without whsec_), \"{id}.{timestamp}.{raw body}\")).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              },
              "example": {
                "url": "https://api.your-app.com/hooks/scribe",
                "events": [
                  "job.succeeded",
                  "job.failed"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreated"
                },
                "example": {
                  "id": "a4f81c2d-6e93-40b7-95d2-3c08e7b1f649",
                  "object": "webhook_endpoint",
                  "url": "https://api.your-app.com/hooks/scribe",
                  "events": [
                    "job.succeeded",
                    "job.failed"
                  ],
                  "secret": "whsec_Zks3JprXcO1FaK9yTqR2v8wBnE5dLmHu"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints",
        "description": "Signing secrets are never included in listings.",
        "responses": {
          "200": {
            "description": "Registered endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpointSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "webhooks": {
    "job.succeeded": {
      "post": {
        "summary": "A job finished successfully",
        "description": "Sent to every active endpoint subscribed to job.succeeded. One delivery attempt with a 10-second timeout; any 2xx acknowledges. Verify the webhook-signature header before trusting the payload.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx to acknowledge receipt."
          }
        }
      }
    },
    "job.failed": {
      "post": {
        "summary": "A job failed",
        "description": "Sent after the job is marked failed and its credits are refunded. data.error carries the failure reason.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx to acknowledge receipt."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from Dashboard → API Keys. Format: wz_live_… (production) or wz_test_… (development). Send as: Authorization: Bearer wz_live_…"
      }
    },
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Resource ID (UUID)."
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "The key's per-minute request budget. Present on every authenticated response.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current 60-second window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Unix seconds when the current window resets.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation failed. The message lists each violation as \"field: problem\" joined with \"; \".",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_request",
                "message": "topic: String must contain at least 4 character(s)"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, unknown, or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_api_key",
                "message": "Missing or invalid API key. Pass it as: Authorization: Bearer wz_live_..."
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Insufficient credits. Nothing was charged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "insufficient_credits",
                "message": "Insufficient credits: need 25, have 8. Top up or upgrade your plan."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource doesn't exist or belongs to another organization.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "Article not found"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-key request budget exhausted for the current 60-second window. Wait retry-after seconds.",
        "headers": {
          "retry-after": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the window resets (minimum 1)."
          },
          "x-ratelimit-limit": {
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix seconds when the window resets."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "rate_limit_exceeded",
                "message": "Too many requests. Back off and retry."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error. Safe to retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "Something went wrong on our side."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code: invalid_request, invalid_api_key, insufficient_credits, not_found, rate_limit_exceeded, internal_error, publish_failed.",
                "examples": [
                  "invalid_request"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreateArticleRequest": {
        "type": "object",
        "required": [
          "topic"
        ],
        "properties": {
          "topic": {
            "type": "string",
            "minLength": 4,
            "maxLength": 500,
            "description": "What the article is about."
          },
          "target_keyword": {
            "type": "string",
            "maxLength": 200,
            "description": "Primary SEO keyword. Defaults to the topic."
          },
          "brand_profile_id": {
            "type": "string",
            "description": "Grounds the article in this profile's knowledge base and voice. 404 if not owned by your org."
          },
          "mode": {
            "type": "string",
            "enum": [
              "standard",
              "deep_research"
            ],
            "default": "standard",
            "description": "standard = 10 credits; deep_research = 25 credits, more research on a stronger model stack."
          },
          "language": {
            "type": "string",
            "maxLength": 12,
            "default": "en",
            "description": "Output language code. Non-English adds 5 credits and is culturally adapted."
          },
          "target_words": {
            "type": "integer",
            "minimum": 400,
            "maximum": 4000,
            "description": "Target length. The pipeline aims for 600-3000 and defaults to 1600."
          },
          "tone": {
            "type": "string",
            "maxLength": 300
          },
          "audience": {
            "type": "string",
            "maxLength": 300
          },
          "extra_instructions": {
            "type": "string",
            "maxLength": 2000
          },
          "image": {
            "type": "boolean",
            "default": false,
            "description": "Generate a hero image (+3 credits)."
          },
          "derivatives": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "x_thread",
                "linkedin",
                "newsletter"
              ]
            },
            "default": [],
            "description": "Repurposed formats (+3 credits flat for any non-empty selection)."
          }
        }
      },
      "UpdateArticleRequest": {
        "type": "object",
        "description": "At least one field is required.",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 300,
            "description": "Replacement headline."
          },
          "content_markdown": {
            "type": "string",
            "maxLength": 200000,
            "description": "Replacement body. content_html and the derived counts are regenerated from it."
          },
          "meta_title": {
            "type": "string",
            "maxLength": 200,
            "description": "SERP title tag."
          },
          "meta_description": {
            "type": "string",
            "maxLength": 500,
            "description": "SERP description."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "in_review",
              "approved",
              "published"
            ],
            "description": "Move the article through its lifecycle."
          }
        }
      },
      "JobAck": {
        "type": "object",
        "required": [
          "id",
          "object",
          "type",
          "status",
          "credits_charged",
          "replayed",
          "links"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "job"
          },
          "type": {
            "type": "string",
            "examples": [
              "article"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "succeeded",
              "failed",
              "canceled"
            ]
          },
          "credits_charged": {
            "type": "integer"
          },
          "replayed": {
            "type": "boolean",
            "description": "true when an Idempotency-Key matched an existing job."
          },
          "links": {
            "type": "object",
            "required": [
              "self"
            ],
            "properties": {
              "self": {
                "type": "string"
              }
            }
          }
        }
      },
      "JobStage": {
        "type": "object",
        "required": [
          "key",
          "label",
          "status"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Article pipeline: recon, plan, research, draft, edit, seo, score, image (if requested), derivatives (if requested)."
          },
          "label": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "done",
              "error"
            ]
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "endedAt": {
            "type": "string",
            "format": "date-time"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "object",
          "type",
          "status",
          "stages",
          "result",
          "credits_charged",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "job"
          },
          "type": {
            "type": "string",
            "enum": [
              "article",
              "ingest_source"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "succeeded",
              "failed",
              "canceled"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Key of the currently (or last) running stage."
          },
          "stages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobStage"
            }
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "description": "Empty until success. Article jobs: {article_id, title, word_count, quality_score, citations}. Ingest jobs: {pages, chunks}."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "credits_charged": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Citation": {
        "type": "object",
        "required": [
          "claim",
          "url",
          "title"
        ],
        "properties": {
          "claim": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        }
      },
      "Article": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "mode",
          "language",
          "topic",
          "title",
          "citations",
          "title_variants",
          "faq",
          "json_ld",
          "word_count",
          "read_minutes",
          "derivatives",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "article"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "in_review",
              "approved",
              "published"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "standard",
              "deep_research"
            ]
          },
          "language": {
            "type": "string"
          },
          "topic": {
            "type": "string"
          },
          "target_keyword": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta_description": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_markdown": {
            "type": "string",
            "description": "Omitted when ?format=html."
          },
          "content_html": {
            "type": "string",
            "description": "Omitted when ?format=markdown."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "title_variants": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "3 alternative titles for A/B testing."
          },
          "faq": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "q",
                "a"
              ],
              "properties": {
                "q": {
                  "type": "string"
                },
                "a": {
                  "type": "string"
                }
              }
            }
          },
          "json_ld": {
            "type": "object",
            "additionalProperties": true,
            "description": "schema.org @graph: BlogPosting + FAQPage (when FAQ exists)."
          },
          "word_count": {
            "type": "integer"
          },
          "read_minutes": {
            "type": "number"
          },
          "flesch_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Flesch Reading Ease; English articles only."
          },
          "quality_score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "0-100 LLM rubric; 80+ = publishable without edits."
          },
          "quality_breakdown": {
            "type": "object",
            "additionalProperties": true,
            "description": "{overall, clarity, hook, specificity, information_gain, notes}."
          },
          "hero_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "derivatives": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "content"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "x_thread",
                    "linkedin",
                    "newsletter"
                  ]
                },
                "content": {
                  "description": "x_thread: array of tweet strings. linkedin: {text}. newsletter: {subject_lines, preview, body_markdown}."
                }
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ArticleSummary": {
        "type": "object",
        "required": [
          "id",
          "title",
          "status",
          "mode",
          "language",
          "word_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "in_review",
              "approved",
              "published"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "standard",
              "deep_research"
            ]
          },
          "language": {
            "type": "string"
          },
          "word_count": {
            "type": "integer"
          },
          "quality_score": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublishRequest": {
        "type": "object",
        "required": [
          "integration_id"
        ],
        "properties": {
          "integration_id": {
            "type": "string",
            "description": "A CMS integration configured at Dashboard → Integrations."
          },
          "as_draft": {
            "type": "boolean",
            "default": false,
            "description": "Create the post unpublished in the CMS."
          }
        }
      },
      "PublishResult": {
        "type": "object",
        "required": [
          "object",
          "status",
          "external_id"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "publish"
          },
          "status": {
            "type": "string",
            "const": "published"
          },
          "external_id": {
            "type": "string",
            "description": "Post/document/entry ID in the CMS."
          },
          "external_url": {
            "type": "string",
            "description": "Public URL when the CMS returns one (WordPress does)."
          }
        }
      },
      "CreateBrandProfileRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "website_url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "industry": {
            "type": "string",
            "maxLength": 200
          },
          "audience": {
            "type": "string",
            "maxLength": 500
          },
          "default_language": {
            "type": "string",
            "maxLength": 12,
            "default": "en"
          },
          "ingest_website": {
            "type": "boolean",
            "default": true,
            "description": "Crawl website_url into the knowledge base immediately."
          }
        }
      },
      "BrandProfileAck": {
        "type": "object",
        "required": [
          "id",
          "object",
          "name",
          "voice_status",
          "ingest_job_id"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "brand_profile"
          },
          "name": {
            "type": "string"
          },
          "website_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "voice_status": {
            "type": "string",
            "enum": [
              "none",
              "extracting",
              "ready"
            ]
          },
          "ingest_job_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The website ingest job, when one was started."
          }
        }
      },
      "BrandProfileSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "voice_status",
          "default_language",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "website_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "voice_status": {
            "type": "string",
            "enum": [
              "none",
              "extracting",
              "ready"
            ]
          },
          "default_language": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateSourceRequest": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "website",
              "url",
              "text",
              "writing_sample"
            ],
            "description": "website crawls the whole site; url ingests one page; text ingests raw text; writing_sample also drives voice extraction."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Required for website and url types."
          },
          "title": {
            "type": "string",
            "maxLength": 300
          },
          "content": {
            "type": "string",
            "maxLength": 200000,
            "description": "Required for text and writing_sample types."
          }
        }
      },
      "SourceAck": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "ingest_job_id"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "kb_source"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "crawling",
              "processing",
              "ready",
              "error"
            ]
          },
          "ingest_job_id": {
            "type": "string"
          }
        }
      },
      "KbSource": {
        "type": "object",
        "required": [
          "id",
          "type",
          "status",
          "page_count",
          "chunk_count"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "website",
              "url",
              "text",
              "writing_sample"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "crawling",
              "processing",
              "ready",
              "error"
            ]
          },
          "page_count": {
            "type": "integer"
          },
          "chunk_count": {
            "type": "integer"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "required": [
          "object",
          "days",
          "credit_balance",
          "daily"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "usage"
          },
          "days": {
            "type": "integer"
          },
          "credit_balance": {
            "type": "integer"
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "day",
                "credits",
                "requests",
                "tokens_in",
                "tokens_out"
              ],
              "properties": {
                "day": {
                  "type": "string",
                  "format": "date"
                },
                "credits": {
                  "type": "integer"
                },
                "requests": {
                  "type": "integer"
                },
                "tokens_in": {
                  "type": "integer"
                },
                "tokens_out": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Must be https in production."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "job.succeeded",
                "job.failed"
              ]
            },
            "default": [
              "job.succeeded",
              "job.failed"
            ]
          }
        }
      },
      "WebhookEndpointCreated": {
        "type": "object",
        "required": [
          "id",
          "object",
          "url",
          "events",
          "secret"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "webhook_endpoint"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "job.succeeded",
                "job.failed"
              ]
            }
          },
          "secret": {
            "type": "string",
            "description": "whsec_ signing secret — returned only in this response."
          }
        }
      },
      "WebhookEndpointSummary": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "active",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "job.succeeded",
                "job.failed"
              ]
            }
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "job.succeeded",
              "job.failed"
            ]
          },
          "data": {
            "type": "object",
            "description": "The job payload: id, type, status, result, error, credits_charged, created_at, finished_at.",
            "required": [
              "id",
              "type",
              "status",
              "result",
              "credits_charged",
              "created_at"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "article",
                  "ingest_source"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "succeeded",
                  "failed"
                ]
              },
              "result": {
                "type": "object",
                "additionalProperties": true
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "credits_charged": {
                "type": "integer"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "finished_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      }
    }
  }
}
