{"openapi":"3.1.0","info":{"title":"Pitchwire REST API","version":"v1","description":"Pitchwire is a programmatic press release platform. Same product, two surfaces: this REST API and an MCP server at /mcp. AI agents and traditional clients use the same endpoints, the same auth, and the same rate limits.","contact":{"name":"Pitchwire developers","url":"https://pitchwire.ai/developers","email":"hello@pitchwire.ai"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://pitchwire.ai","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Account","description":"Account / credit checks for agents."},{"name":"Releases","description":"Submit, edit, list, publish, and distribute releases."},{"name":"Embed","description":"Public read-only embed widget for newsroom integration."}],"paths":{"/api/v1/account":{"get":{"tags":["Account"],"summary":"Get account credits + plans","description":"Returns the authenticated account's credit balance, plan list, and a Stripe checkout URL hint for purchasing more credits.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Account not found."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Internal error."}}}},"/api/v1/releases":{"get":{"tags":["Releases"],"summary":"List releases","parameters":[{"name":"limit","in":"query","description":"Max releases to return (default 50, max 100).","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"status","in":"query","description":"Filter by status.","schema":{"type":"string","enum":["draft","needs_revision","approved","published"]}}],"responses":{"200":{"description":"Array of releases.","content":{"application/json":{"schema":{"type":"object","properties":{"releases":{"type":"array","items":{"$ref":"#/components/schemas/Release"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Releases"],"summary":"Submit a release for editorial review","description":"Runs the release through AI polish + AI editorial review and saves it. Returns 402 with a Stripe checkout URL when credits are exhausted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitReleaseRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Release"}}}},"400":{"description":"Validation error (malformed body)."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Payment Required — credits exhausted; agents should hit checkout_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequired"}}}},"422":{"description":"Validation error (semantically invalid fields)."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/releases/{id}":{"get":{"tags":["Releases"],"summary":"Fetch a single release","parameters":[{"$ref":"#/components/parameters/ReleaseId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Release"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Releases"],"summary":"Edit a release","description":"Updates any combination of mutable fields. Resets `needs_revision` → `draft`. Returns 409 if the release is already `published`.","parameters":[{"$ref":"#/components/parameters/ReleaseId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditReleaseRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Release"}}}},"400":{"description":"Malformed body."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"409":{"description":"Cannot edit a published release."},"422":{"description":"Validation error."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/releases/{id}/publish":{"post":{"tags":["Releases"],"summary":"Publish a release to the branded newsroom","description":"Transitions an `approved` release to `published`, sets `published_at`, and returns the canonical newsroom URL.","parameters":[{"$ref":"#/components/parameters/ReleaseId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"release":{"$ref":"#/components/schemas/Release"},"newsroom_url":{"type":"string","format":"uri"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"409":{"description":"Release is not in `approved` state."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/releases/{id}/distribute":{"post":{"tags":["Releases"],"summary":"Distribute a published release across channels","description":"Wire channels (`ein_presswire`, alias `wire`) queue a `pending` distribution_logs row for manual EIN Presswire submission. Digital channels (`email`, `linkedin`, `x`/`twitter`) proxy to the FastAPI `/api/distribute` pipeline.","parameters":[{"$ref":"#/components/parameters/ReleaseId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DistributeRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"release_id":{"type":"string","format":"uuid"},"results":{"type":"array","items":{"$ref":"#/components/schemas/ChannelOutcome"}}}}}}},"400":{"description":"Malformed body."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found."},"409":{"description":"Release is not `published`."},"422":{"description":"Invalid channels or recipients."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/embed":{"get":{"tags":["Embed"],"summary":"JavaScript embed widget for the newsroom","description":"Returns a JavaScript snippet that renders the most recent published releases for the API key's account. Designed to be `<script src=...>`-embedded on a third-party site. Auth is via the `?key=` query parameter (the same API key used as Bearer for REST), because `<script>` tags cannot send Authorization headers.","security":[],"parameters":[{"name":"key","in":"query","required":true,"description":"API key (same value you would send as Bearer for REST).","schema":{"type":"string"}}],"responses":{"200":{"description":"JavaScript that renders the embed.","content":{"application/javascript":{"schema":{"type":"string"}}}},"400":{"description":"Missing key parameter."},"404":{"description":"API key not found."},"429":{"description":"Rate-limit exceeded (60 req/min/key). Body is JavaScript that logs the error."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key (pk_live_…)"}},"parameters":{"ReleaseId":{"name":"id","in":"path","required":true,"description":"Release UUID.","schema":{"type":"string","format":"uuid"}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded for this route + key combination.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"Account":{"type":"object","properties":{"company_name":{"type":"string"},"credits":{"type":"integer","minimum":0},"api_key_preview":{"type":"string","description":"Masked, like pk_live_…fa30."},"created_at":{"type":"string","format":"date-time"},"plans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["starter","standard","premium"]},"name":{"type":"string"},"price":{"type":"string","description":"Display price like $149."},"credits":{"type":"integer"}}}},"purchase_url":{"type":"string","format":"uri"},"checkout_api":{"type":"string","format":"uri"}}},"Release":{"type":"object","description":"Canonical release shape. POST /releases returns this with credits_remaining + pipeline; GET /releases/:id returns this without pipeline; PATCH returns this. Field availability depends on the operation; see endpoint responses.","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["draft","needs_revision","approved","published"]},"plan":{"type":"string","enum":["starter","standard","premium"],"nullable":true},"title":{"type":"string"},"slug":{"type":"string"},"dateline":{"type":"string","nullable":true},"body":{"type":"string"},"boilerplate":{"type":"string","nullable":true},"contact_name":{"type":"string","nullable":true},"contact_email":{"type":"string","format":"email","nullable":true},"contact_phone":{"type":"string","nullable":true},"editorial_score":{"type":"integer","minimum":0,"maximum":100,"nullable":true},"editorial_summary":{"type":"string","nullable":true},"editorial_issues":{"type":"array","items":{"type":"object"},"nullable":true},"content":{"type":"object","description":"Generated distribution assets.","nullable":true,"properties":{"social_posts":{"type":"object","nullable":true},"email_subject":{"type":"string","nullable":true},"email_preview":{"type":"string","nullable":true},"schema_org":{"type":"object","nullable":true}}},"credits_remaining":{"type":"integer","nullable":true,"description":"Remaining account credits after this operation. Present on POST /releases responses."},"newsroom_url":{"type":"string","format":"uri","nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"pipeline":{"type":"object","nullable":true,"description":"Pipeline diagnostic fields (whether polish/review succeeded). Present on POST /releases.","properties":{"polish_failed":{"type":"boolean"},"review_failed":{"type":"boolean"}}}}},"SubmitReleaseRequest":{"type":"object","required":["title","body"],"properties":{"title":{"type":"string","maxLength":300},"body":{"type":"string","maxLength":50000},"dateline":{"type":"string","maxLength":2000},"boilerplate":{"type":"string","maxLength":2000},"contact_name":{"type":"string","maxLength":2000},"contact_email":{"type":"string","format":"email","maxLength":2000},"contact_phone":{"type":"string","maxLength":2000},"plan":{"type":"string","enum":["starter","standard","premium"],"default":"starter"},"polish":{"type":"boolean","default":true,"description":"Run the AI AP-style polish pass before editorial review. Set to false to skip polish and review the original text only."},"webhook_url":{"type":"string","format":"uri","description":"Optional URL Pitchwire will POST review/publish events to (HMAC-SHA256 signed)."},"idempotency_key":{"type":"string","description":"Client-supplied key to dedupe retries within a 24h window. May also be passed as the Idempotency-Key header."}}},"EditReleaseRequest":{"type":"object","description":"All fields optional; provide any combination of mutable fields.","properties":{"title":{"type":"string","maxLength":300},"body":{"type":"string","maxLength":50000},"dateline":{"type":"string","maxLength":2000},"boilerplate":{"type":"string","maxLength":2000},"contact_name":{"type":"string","maxLength":2000},"contact_email":{"type":"string","format":"email","maxLength":2000},"contact_phone":{"type":"string","maxLength":2000},"plan":{"type":"string","enum":["starter","standard","premium"]}}},"DistributeRequest":{"type":"object","required":["channels"],"properties":{"channels":{"type":"array","minItems":1,"items":{"type":"string","enum":["ein_presswire","wire","email","linkedin","x","twitter"]}},"recipients":{"type":"array","description":"Optional explicit recipient list for digital channels.","items":{"type":"string"}}}},"ChannelOutcome":{"type":"object","properties":{"channel":{"type":"string"},"status":{"type":"string","enum":["pending","sent","skipped","failed"]},"detail":{"type":"string"},"dry_run":{"type":"boolean"}}},"PaymentRequired":{"type":"object","description":"Returned with HTTP 402 when the authenticated account has zero credits. Agents should either open `purchase_url` in the user's browser or POST to `checkout_api` to obtain a Stripe Checkout URL.","required":["error","credits","purchase_url","checkout_api","checkout_hint"],"properties":{"error":{"type":"string"},"credits":{"type":"integer","minimum":0,"description":"Always 0 in this response shape."},"purchase_url":{"type":"string","format":"uri","description":"Hosted pricing page that lets a human pick a plan and complete checkout."},"checkout_api":{"type":"string","format":"uri","description":"Endpoint an agent can POST {plan} to in order to obtain a Stripe Checkout URL."},"checkout_hint":{"type":"string","description":"Human-readable hint describing how to use checkout_api."}}}}}}