openapi: 3.2.0 info: title: EpicDirectories orders (admin) API description: API-first directory data platform. version: '1.0' contact: {} servers: - url: https://api.epicdirectories.com description: Production - url: http://localhost:3333 description: Local development tags: - name: orders (admin) paths: /v1/directories/{directoryId}/orders: get: operationId: OrdersOperatorController_list summary: List content orders for a directory description: 'Rows newest first, each with the payment that bought it (amount, currency and — the one worth noticing before doing a day of work — whether it has since been refunded). Also returns `countsByStatus` across the whole directory and `openCount`, the number still awaiting work. A comped order has `payment: null`, which is not a missing row. `status=open` is a filter alias for `received` or `in_progress` — no row holds that value; it is the answer to "what needs work", which spans two stored statuses.' parameters: - name: directoryId required: true in: path schema: type: string - name: status required: false in: query description: Omit for every status. `open` is a FILTER ALIAS, not a stored value — it means `received` or `in_progress`, which is the answer to "what needs work". It is what the queue page asks for by default, and `openCount` in the response is its size. schema: enum: - open - received - in_progress - delivered - cancelled - refunded type: string - name: kind required: false in: query schema: enum: - guest_post - niche_edit type: string - name: limit required: false in: query description: Default 50, max 200 schema: type: string - name: offset required: false in: query schema: type: string responses: '200': description: Paginated orders, counts by status, and openCount '404': description: Directory not found or not yours tags: - orders (admin) security: - bearer: [] post: operationId: OrdersOperatorController_createComped summary: Record a comped order — work promised without a payment description: 'The comp path, and the same argument as granting a boost by hand: comping is real, and the alternative is writing a fake Payment row to make the queue look right, which would corrupt the one table that reconciles against Stripe. So this creates **no Payment row** and the order''s `paymentId` stays null. Every field but `kind` is optional. An operator arranging a guest post over email may have only an address at this point, and refusing the row until the brief is complete is how the order ends up not recorded at all. Fires `order.received`, with the same keys a paid order sends and nulls where the payment would have been.' parameters: - name: directoryId required: true in: path schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateCompedOrderDto' responses: '201': description: The created order '400': description: kind was not guest_post or niche_edit '404': description: Directory not found or not yours tags: - orders (admin) security: - bearer: [] /v1/directories/{directoryId}/orders/{orderId}: get: operationId: OrdersOperatorController_get summary: Get one content order description: 'Includes the buyer''s brief. Treat every buyer-supplied field as untrusted text: it is whatever was typed into a Stripe custom field, and `targetUrl` rendered straight into an `href` turns a purchase into a stored redirect.' parameters: - name: directoryId required: true in: path schema: type: string - name: orderId required: true in: path schema: type: string responses: '200': description: The order '404': description: Order not found in this directory tags: - orders (admin) security: - bearer: [] patch: operationId: OrdersOperatorController_update summary: Advance an order, record where it was published, or leave a note description: 'Send any of `status`, `placementUrl`, `operatorNotes`. An empty body is a 400 rather than a no-op. **`delivered` requires a `placementUrl`**, already stored or sent in the same request. The rule is checked against the resulting state, so clearing the URL of a delivered order is refused as well. **`refunded` cannot be set by hand, and an order already refunded answers 409 to any status change.** A refund is Stripe''s fact, arriving over the signed webhook. If the work was done anyway, record the placement URL and a note — that says what happened without overwriting the fact that the money went back. Reaching `delivered` for the first time fires `order.delivered`. Later edits to a delivered order do not re-fire it, and do not move `deliveredAt`.' parameters: - name: directoryId required: true in: path schema: type: string - name: orderId required: true in: path schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateOrderDto' responses: '200': description: The updated order '400': description: Empty body, an unsettable status, a placementUrl that is not an https URL, or `delivered` without one '404': description: Order not found in this directory '409': description: The order was refunded; its status is fixed tags: - orders (admin) security: - bearer: [] components: schemas: UpdateOrderDto: type: object properties: status: type: string enum: - received - in_progress - delivered - cancelled description: '`refunded` is deliberately not accepted. A refund is Stripe''s fact and arrives over the signed webhook, which sets it; a status typed here could claim money went back when it did not. An order already refunded answers 409 to any status change.' placementUrl: type: string description: Where it was published. **Required in order to reach `delivered`** — it is the only field that lets anyone but the operator confirm the buyer got what they paid for. Send an empty string to clear it, which is refused while the order is delivered. operatorNotes: type: string description: Operator-only. Never rendered publicly. Empty string clears it. CreateCompedOrderDto: type: object properties: kind: type: string enum: - guest_post - niche_edit purchaserEmail: type: string description: 'Who the work is discussed with. Not validated as an email: this is a note to the operator, and nothing sends mail to it — there is no email infrastructure in this system.' targetUrl: type: string description: Link destination the post should point at. anchorText: type: string brief: type: string description: Topic, a link to a draft, or instructions. operatorNotes: type: string description: Why it was comped, ideally. required: - kind securitySchemes: bearer: scheme: bearer bearerFormat: JWT type: http apiKey: type: apiKey in: header name: x-api-key x-provenance: method: harvested publisher: EpicDirectories source: https://api.epicdirectories.com/openapi.json note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json); stamped by stamp-openapi-provenance.py (roadmap#793)