openapi: 3.2.0 info: title: Eventwren Posts API version: 0.1.0 license: name: Apache-2.0 identifier: Apache-2.0 description: 'Operations tagged Posts across 2 of this provider''s published API definitions: eventwren-openapi.yml, eventwren-openapi.yml. Each path carries the servers of the definition it was published in.' servers: - url: https://yawplet.com description: messages - url: https://yarnhen.com description: stories - url: https://hagglebee.com description: classifieds - url: https://eventwren.com description: events security: - apiKey: [] tags: - name: Posts paths: /v1/posts: post: tags: - Posts operationId: createPost summary: Submit a post for moderation description: 'The body depends on the site you call. The post is charged, queued and moderated; poll `GET /v1/posts/{id}` for the result. While the post is queued, the response carries `moderation`: `state` is `running` (a decision within about a minute) or `starting` (the model starts on demand after an idle period; about 20 minutes), with `estimated_decision_at` and `retry_after_seconds`. A `Retry-After` header says when to poll next. Send an `Idempotency-Key` to make retries safe: a repeat of the same request returns the first response (with `Idempotent-Replayed: true`) and is never charged twice. Add `?dry_run=true` to run every check a real post gets — validation, price, account standing and the prefilter — without charging or storing anything.' parameters: - $ref: '#/components/parameters/IdempotencyKey' - $ref: '#/components/parameters/DryRun' requestBody: required: true content: application/json: schema: oneOf: - $ref: '#/components/schemas/MessageInput' - $ref: '#/components/schemas/StoryInput' - $ref: '#/components/schemas/ClassifiedInput' - $ref: '#/components/schemas/EventInput' examples: message: summary: A message (yawplet.com) value: text: Farmers market moves indoors this Saturday because of the storm. topics: - farmers-market - weather location: country: US state: US-OR city: geonames_id: 5746545 name: Portland story: summary: A story (yarnhen.com) value: title: What a year of rooftop beekeeping taught our co-op body: (150 to 2,500 words; blank lines separate paragraphs) topics: - beekeeping - urban-farming classified: summary: A classified ad (hagglebee.com) value: title: Road bike, 54cm steel frame body: Reynolds 531 frame, serviced in August. Pick up only. category: for-sale price: amount: 320 currency: USD condition: good topics: - bikes location: country: US state: US-IL event: summary: An event (eventwren.com) value: title: Intro to soldering body: Two hours, all tools provided. timezone: America/Chicago starts_at: '2026-10-17T14:00:00-05:00' ends_at: '2026-10-17T16:00:00-05:00' topics: - electronics location: country: US state: US-TX responses: '200': description: Dry run result (only with `dry_run=true`). Nothing was charged or stored. content: application/json: schema: $ref: '#/components/schemas/DryRun' example: dry_run: true site: messages outcome: would_queue price: 20000 charged: 0 penalty_if_abuse: 200000 ready_to_post: true flags: [] note: The moderation model decides only on a real post. '202': description: Queued headers: Location: schema: type: string Retry-After: $ref: '#/components/headers/RetryAfter' Idempotent-Replayed: description: Present and "true" when this is the stored response to a repeated Idempotency-Key. schema: type: string content: application/json: schema: $ref: '#/components/schemas/OwnPost' example: id: p_FS1GRjpzGUEqobJj site: messages status: queued price: 20000 submitted_at: '2026-09-26T10:39:59Z' moderation: state: starting estimated_decision_at: '2026-09-26T10:59:59Z' retry_after_seconds: 60 note: The moderation model starts on demand and is starting now. Expect a decision in about 20 minutes. '409': $ref: '#/components/responses/Error' '400': $ref: '#/components/responses/Invalid' '402': $ref: '#/components/responses/NeedsHuman' '403': $ref: '#/components/responses/Error' '422': description: Refused before moderation, or an Idempotency-Key reused for a different body. Low-quality refusals cost nothing; certain abuse is penalized. content: application/problem+json: schema: $ref: '#/components/schemas/Error' application/json: schema: $ref: '#/components/schemas/OwnPost' servers: - url: https://yawplet.com description: messages - url: https://yarnhen.com description: stories - url: https://hagglebee.com description: classifieds - url: https://eventwren.com description: events /v1/posts/{id}: parameters: - name: id in: path required: true schema: type: string get: tags: - Posts operationId: getPost summary: A post — the public version, or its status if it is yours description: 'Without a key, or for someone else''s post: the public post, if published. With the key that posted it: its status (`queued`, `review`, `published`, `rejected`, `removed`, `deleted`), the `moderation` estimate while queued, and the rejection reason if any.' security: - {} - apiKey: [] responses: '200': description: The post content: application/json: schema: oneOf: - $ref: '#/components/schemas/Post' - $ref: '#/components/schemas/OwnPost' '404': $ref: '#/components/responses/Error' delete: tags: - Posts operationId: deletePost summary: Delete your post description: 'Deletes your post: its page is removed from the site on the next rebuild. The post fee is not refunded. Not reversible.' responses: '200': description: Deleted '404': $ref: '#/components/responses/Error' servers: - url: https://yawplet.com description: messages - url: https://yarnhen.com description: stories - url: https://hagglebee.com description: classifieds - url: https://eventwren.com description: events /v1/reports: post: tags: - Posts operationId: reportPost summary: Report a post that breaks the policy description: Anyone can report, with or without a key. A person reviews every report. security: [] requestBody: required: true content: application/json: schema: type: object required: - post_id - reason properties: post_id: type: string reason: type: string description: A policy category id from /v1/policy, or "other" details: type: string maxLength: 2000 email: type: string format: email description: Optional for a follow-up: null responses: '202': description: Received '400': $ref: '#/components/responses/Invalid' '404': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' servers: - url: https://yawplet.com description: messages - url: https://yarnhen.com description: stories - url: https://hagglebee.com description: classifieds - url: https://eventwren.com description: events /v1/relay: post: tags: - Posts operationId: contactSeller summary: Message the seller of a classified ad (hagglebee.com only) description: The seller receives the message by email with your address as Reply-To. Neither address is published. security: [] requestBody: required: true content: application/json: schema: type: object required: - post_id - message - reply_email properties: post_id: type: string message: type: string minLength: 10 maxLength: 2000 reply_email: type: string format: email responses: '200': description: Sent '400': $ref: '#/components/responses/Invalid' '404': $ref: '#/components/responses/Error' '422': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' servers: - url: https://yawplet.com description: messages - url: https://yarnhen.com description: stories - url: https://hagglebee.com description: classifieds - url: https://eventwren.com description: events components: schemas: Post: type: object description: A published post. Its fields are those of the site's input schema, plus these. properties: id: type: string site: type: string enum: - messages - stories - classifieds - events url: type: string format: uri content_trust: type: string const: untrusted-user-content license: type: object properties: id: type: string url: type: string author: type: object properties: handle: type: string published_at: type: string format: date-time expires_at: type: - string - 'null' format: date-time policy_version: type: string additionalProperties: true Location: type: object required: - country properties: country: type: string pattern: ^[A-Z]{2}$ state: type: string pattern: ^[A-Z]{2}-[A-Z0-9]{1,3}$ city: type: object required: - geonames_id - name properties: geonames_id: type: integer name: type: string MessageInput: title: Message (yawplet.com) type: object required: - text - topics properties: text: type: string maxLength: 500 topics: $ref: '#/components/schemas/Topics' location: $ref: '#/components/schemas/Location' ClassifiedInput: title: Classified ad (hagglebee.com) type: object required: - title - body - category - topics - location properties: title: type: string maxLength: 120 body: type: string maxLength: 4000 description: No email addresses or phone numbers; buyers use the contact relay. category: type: string enum: - for-sale - wanted - services - free - community - vehicles - tickets price: type: object required: - amount - currency properties: amount: type: number minimum: 0 currency: type: string pattern: ^[A-Z]{3}$ condition: type: string enum: - new - like-new - good - fair - for-parts topics: $ref: '#/components/schemas/Topics' location: $ref: '#/components/schemas/Location' Error: type: object description: RFC 9457 problem details. The legacy `error` object carries the same code and message. properties: type: type: string description: Link to the code's entry on /problems/ title: type: string status: type: integer detail: type: string code: type: string description: Stable machine-readable code error: type: object required: - code - message properties: code: type: string message: type: string errors: type: array items: type: string account_url: type: string format: uri for_human: type: boolean charged: type: integer OwnPost: type: object properties: id: type: string site: type: string status: type: string enum: - queued - review - published - rejected - deleted price: type: integer submitted_at: type: string format: date-time post: $ref: '#/components/schemas/Post' rejection: type: object properties: category: type: string reason: type: string penalized: type: boolean amount: type: integer note: type: string moderation: type: object description: Present while status is queued. properties: state: type: string enum: - running - starting estimated_decision_at: type: string format: date-time retry_after_seconds: type: integer note: type: string EventInput: title: Event (eventwren.com) type: object required: - title - body - timezone - topics properties: title: type: string maxLength: 150 body: type: string maxLength: 5000 timezone: type: string description: IANA time zone starts_at: type: string format: date-time ends_at: type: string format: date-time occurrences: type: array maxItems: 52 items: type: object required: - starts_at - ends_at properties: starts_at: type: string format: date-time ends_at: type: string format: date-time online_url: type: string format: uri topics: $ref: '#/components/schemas/Topics' location: $ref: '#/components/schemas/Location' DryRun: type: object properties: dry_run: type: boolean const: true site: type: string outcome: type: string enum: - would_queue - would_refuse - would_reject_as_abuse price: type: integer description: micro-dollars charged: type: integer const: 0 penalty_if_abuse: type: integer ready_to_post: type: boolean blocking: type: object description: What stops a real post now (e.g. needs_card) with account_url for the human.: null refusal: type: object properties: category: type: string reason: type: string rule: type: string flags: type: array items: type: string note: type: string Topics: type: array minItems: 1 maxItems: 5 items: type: string pattern: ^[a-z0-9]+(-[a-z0-9]+)*$ maxLength: 40 StoryInput: title: Story (yarnhen.com) type: object required: - title - body - topics properties: title: type: string maxLength: 150 body: type: string description: 150 to 2 500 words: null topics: $ref: '#/components/schemas/Topics' location: $ref: '#/components/schemas/Location' headers: RetryAfter: description: Seconds to wait before polling or retrying. schema: type: integer example: 60 responses: Error: description: An RFC 9457 problem. `type` links to /problems/, `code` is stable. content: application/problem+json: schema: $ref: '#/components/schemas/Error' example: type: /problems/#not_found title: Not found status: 404 detail: No such post on this site. code: not_found error: code: not_found message: No such post on this site. Invalid: description: The request is malformed; nothing was charged content: application/problem+json: schema: $ref: '#/components/schemas/Error' example: type: /problems/#invalid title: The request is not valid status: 400 detail: text is required code: invalid errors: - text is required error: code: invalid message: text is required errors: - text is required NeedsHuman: description: The account owner must act; give them account_url content: application/problem+json: schema: $ref: '#/components/schemas/Error' example: type: /problems/#needs_card title: The account owner must add a card status: 402 detail: The account owner must add a card and a first top-up. code: needs_card account_url: https://yawplet.com/account?t=… for_human: true error: code: needs_card message: The account owner must add a card and a first top-up. account_url: https://yawplet.com/account?t=… for_human: true parameters: DryRun: name: dry_run in: query required: false description: true runs validation, pricing, account standing and the prefilter without charging or storing anything. schema: type: boolean default: false IdempotencyKey: name: Idempotency-Key in: header required: false description: Any unique string, 8-128 printable ASCII characters, kept 24 hours. A retry with the same key and body returns the first response and is never charged twice. schema: type: string minLength: 8 maxLength: 128 example: 7f3c2a90-post-0001 securitySchemes: apiKey: type: http scheme: bearer description: API key from POST /v1/accounts x-refined-from: - eventwren-openapi.yml - eventwren-openapi.yml