{
  "openapi": "3.1.0",
  "info": {
    "title": "CoinBrowse Publisher API",
    "version": "1.0.0",
    "summary": "Authorized Community automation for approved organizations.",
    "description": "Publisher API v1 automates posts, comments, nested replies and voting under an approved organization's own Community account.\n\nIt automates what that account could already do by hand. It does not create a second identity, add vote weight, buy ranking, verification or preferential moderation, or vote on behalf of anyone else. API-created content appears exactly as manual content from the same account: no automation label, and `bot_account` stays false.\n\n**Asynchronous by design.** Every mutation is durably accepted, answered with `202` and a `request_id`, and applied by a worker. Poll `GET /operations/{request_id}`.\n\n**No link posts and no images in v1.** `POST /posts` accepts a title and a Markdown body only. A `url`, `external_url`, `custom_thumbnail` or image field is rejected as an unknown field rather than ignored.\n\n**No bulk anything.** There is no batch post, comment, reply or vote endpoint, and one vote request addresses exactly one target.\n\nBilling is not part of this API. Invoices, crypto-payment detection, pricing pages and account dashboards are a later phase and are not live.",
    "contact": {
      "name": "CoinBrowse",
      "email": "hello@coinbrowse.com"
    }
  },
  "servers": [
    {
      "url": "https://coinbrowse.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "posts",
      "description": "Top-level posts, published to the community fixed by organization type."
    },
    {
      "name": "comments",
      "description": "Top-level comments and nested replies. One endpoint serves both."
    },
    {
      "name": "votes",
      "description": "The organization's single native vote on one allowed target."
    },
    {
      "name": "operations",
      "description": "Generic status for every accepted mutation."
    },
    {
      "name": "usage",
      "description": "Rolling-24h quota, one bucket per action family."
    }
  ],
  "paths": {
    "/posts": {
      "post": {
        "tags": [
          "posts"
        ],
        "operationId": "createPost",
        "summary": "Create a top-level post",
        "description": "Publishes to the community fixed by the organization's approved type. No destination, community, account, user, url or image field is accepted; unknown fields are rejected. Consumes one `posts` slot only on success.",
        "security": [
          {
            "apiKey": [
              "posts:create"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "title",
                  "body_markdown"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "body_markdown": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000,
                    "description": "Markdown. Validated https/http/mailto links are allowed and are never fetched server-side. Image embeds, raw HTML, other URL schemes, control characters and bidirectional formatting are rejected."
                  }
                }
              },
              "examples": {
                "update": {
                  "value": {
                    "title": "Tokpie listing update",
                    "body_markdown": "New pairs are live. Details: [docs](https://example.com/docs)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/posts/{publication_id}": {
      "parameters": [
        {
          "name": "publication_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^pub_[0-9a-f]{32}$"
          },
          "description": "A CoinBrowse identifier. A raw Lemmy post id is never accepted, which is what makes manually created content unreachable through this API."
        }
      ],
      "get": {
        "tags": [
          "posts"
        ],
        "operationId": "getPost",
        "summary": "Read your own API-created post",
        "security": [
          {
            "apiKey": [
              "posts:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The publication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Publication"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "posts"
        ],
        "operationId": "editPost",
        "summary": "Edit your own API-created post",
        "description": "At least one field is required. Editing consumes no creation quota. A URL cannot be introduced.",
        "security": [
          {
            "apiKey": [
              "posts:edit"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "minProperties": 1,
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "body_markdown": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "posts"
        ],
        "operationId": "deletePost",
        "summary": "Delete your own API-created post",
        "description": "Desired-state and idempotent. Deleting never refunds the creation slot. Remains available while an entitlement is expired, so lapsed customers can still clean up.",
        "security": [
          {
            "apiKey": [
              "posts:delete"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/comments": {
      "post": {
        "tags": [
          "comments"
        ],
        "operationId": "createComment",
        "summary": "Create a top-level comment or a nested reply",
        "description": "A `post` target creates a top-level comment and returns operation `comment_create`; a `comment` target creates a nested reply and returns `reply_create`. Both consume the same `comments_and_replies` bucket. The target must be public local content in an approved community, not removed or deleted, and not on a locked post.",
        "security": [
          {
            "apiKey": [
              "comments:create"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "target",
                  "body_markdown"
                ],
                "properties": {
                  "target": {
                    "$ref": "#/components/schemas/Target"
                  },
                  "body_markdown": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              },
              "examples": {
                "comment": {
                  "summary": "Top-level comment on a post",
                  "value": {
                    "target": {
                      "type": "post",
                      "id": "123"
                    },
                    "body_markdown": "A relevant response."
                  }
                },
                "reply": {
                  "summary": "Nested reply to a comment",
                  "value": {
                    "target": {
                      "type": "comment",
                      "id": "456"
                    },
                    "body_markdown": "A relevant nested reply."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/comments/{comment_id}": {
      "parameters": [
        {
          "name": "comment_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^cmt_[0-9a-f]{32}$"
          },
          "description": "A CoinBrowse identifier. A raw Lemmy comment id is never accepted."
        }
      ],
      "get": {
        "tags": [
          "comments"
        ],
        "operationId": "getComment",
        "summary": "Read your own API-created comment or reply",
        "security": [
          {
            "apiKey": [
              "comments:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The comment or reply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "comments"
        ],
        "operationId": "editComment",
        "summary": "Edit your own API-created comment or reply",
        "security": [
          {
            "apiKey": [
              "comments:edit"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "body_markdown"
                ],
                "properties": {
                  "body_markdown": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "comments"
        ],
        "operationId": "deleteComment",
        "summary": "Delete your own API-created comment or reply",
        "security": [
          {
            "apiKey": [
              "comments:delete"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/votes": {
      "put": {
        "tags": [
          "votes"
        ],
        "operationId": "setVote",
        "summary": "Set, change or remove the organization's vote on one target",
        "description": "`+1` upvote, `0` remove, `-1` downvote. One target per request; there is no batch endpoint.\n\nThis changes the single native vote of the organization's own account. A manual vote and an API vote are the same account-level state, not two votes. Setting the score the account already holds succeeds immediately with `unchanged: true`, applies nothing upstream and consumes no quota. Only a real transition consumes a `vote_state_changes` slot, and only once the upstream change is proven. Removing a vote is a transition; it does not refund an earlier slot.",
        "security": [
          {
            "apiKey": [
              "votes:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "description": "Client-generated. Stored only as a keyed digest. The same key with the same canonical payload returns the original operation; with a different payload it returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "target",
                  "score"
                ],
                "properties": {
                  "target": {
                    "$ref": "#/components/schemas/Target"
                  },
                  "score": {
                    "type": "integer",
                    "enum": [
                      -1,
                      0,
                      1
                    ]
                  }
                }
              },
              "examples": {
                "upvote": {
                  "value": {
                    "target": {
                      "type": "post",
                      "id": "123"
                    },
                    "score": 1
                  }
                },
                "downvote": {
                  "value": {
                    "target": {
                      "type": "comment",
                      "id": "456"
                    },
                    "score": -1
                  }
                },
                "remove": {
                  "value": {
                    "target": {
                      "type": "post",
                      "id": "123"
                    },
                    "score": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account already held this score. Nothing was applied and no quota was consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                }
              }
            }
          },
          "202": {
            "description": "Accepted and durably recorded. The Community action is applied asynchronously; poll the status URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The operation status URL."
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                },
                "description": "Server-generated correlation id, present on every response."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acceptance"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/operations/{request_id}": {
      "get": {
        "tags": [
          "operations"
        ],
        "operationId": "getOperation",
        "summary": "Status of any accepted operation",
        "description": "Covers all eight operation types. `reconciliation_required` means the upstream outcome is not yet confirmed: do not retry the request \u2014 a duplicate is exactly what that state exists to prevent \u2014 and contact support if it persists.",
        "security": [
          {
            "apiKey": [
              "operations:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^req_[0-9a-f]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "usage"
        ],
        "operationId": "getUsage",
        "summary": "Entitlement and rolling-24h quota",
        "description": "Counted on the database clock over a rolling 24-hour window, not a calendar day. Render buckets dynamically: their names are stable, their limits are configuration.",
        "security": [
          {
            "apiKey": [
              "usage:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Current usage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                },
                "example": {
                  "entitlement": {
                    "kind": "paid",
                    "plan": "publisher_api",
                    "status": "active",
                    "starts_at": "2026-09-05T00:00:00Z",
                    "ends_at": "2027-09-05T00:00:00Z"
                  },
                  "window": "rolling_24h",
                  "buckets": {
                    "posts": {
                      "limit": 3,
                      "used": 1,
                      "reserved": 0,
                      "remaining": 2,
                      "next_slot_at": null
                    },
                    "comments_and_replies": {
                      "limit": 15,
                      "used": 4,
                      "reserved": 1,
                      "remaining": 10,
                      "next_slot_at": null
                    },
                    "vote_state_changes": {
                      "limit": 30,
                      "used": 7,
                      "reserved": 0,
                      "remaining": 23,
                      "next_slot_at": null
                    }
                  },
                  "post_destination": "service_providers",
                  "interaction_communities": [
                    "projects",
                    "service_providers"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is not active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No such resource for this organization, or the target is not one this organization may interact with. 404 rather than 403 so existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict, an unusable resource state, a locked target, or authorization that must be renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The request body was too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "This endpoint accepts application/json only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A plan quota bucket is exhausted, or an anti-abuse rate limit was hit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The Community is temporarily unavailable. The operation was not applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer cb_live_<public-id>.<secret>`. The secret is shown once at issuance and stored only as a keyed digest. Never send a key in a query string. Scopes: posts:create, posts:read, posts:edit, posts:delete, comments:create, comments:read, comments:edit, comments:delete, votes:write, operations:read, usage:read. `comments:create` covers both top-level comments and replies. There is no wildcard or administrator scope."
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unsupported_content_type",
                  "payload_too_large",
                  "invalid_api_key",
                  "api_key_inactive",
                  "missing_scope",
                  "organization_inactive",
                  "lemmy_authorization_required",
                  "lemmy_authorization_unhealthy",
                  "entitlement_inactive",
                  "quota_exhausted",
                  "rate_limited",
                  "idempotency_conflict",
                  "resource_not_found",
                  "post_not_editable",
                  "post_not_deletable",
                  "comment_not_editable",
                  "comment_not_deletable",
                  "target_not_found",
                  "target_not_allowed",
                  "target_locked",
                  "invalid_vote_score",
                  "upstream_rejected",
                  "upstream_unavailable",
                  "reconciliation_required",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "Target": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "post",
              "comment"
            ]
          },
          "id": {
            "type": "string",
            "pattern": "^[1-9][0-9]{0,8}$",
            "description": "Local Lemmy numeric id, encoded as a string. Resolved and validated server-side; never treated as ownership evidence."
          }
        },
        "description": "A post target creates a top-level comment; a comment target creates a nested reply. There is deliberately no field for a post or community id: for a reply both are derived from the parent, so a conflicting chain cannot be expressed."
      },
      "Acceptance": {
        "type": "object",
        "required": [
          "request_id",
          "operation",
          "status",
          "status_url"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "pattern": "^req_[0-9a-f]{32}$"
          },
          "operation": {
            "type": "string",
            "enum": [
              "post_create",
              "post_edit",
              "post_delete",
              "comment_create",
              "reply_create",
              "comment_edit",
              "comment_delete",
              "vote_set"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "succeeded",
              "failed",
              "reconciliation_required"
            ]
          },
          "status_url": {
            "type": "string"
          },
          "publication_id": {
            "type": "string",
            "pattern": "^pub_[0-9a-f]{32}$"
          },
          "comment_id": {
            "type": "string",
            "pattern": "^cmt_[0-9a-f]{32}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "comment",
              "reply"
            ]
          },
          "target": {
            "$ref": "#/components/schemas/Target"
          },
          "requested_score": {
            "type": "integer",
            "enum": [
              -1,
              0,
              1
            ]
          }
        }
      },
      "Operation": {
        "type": "object",
        "required": [
          "request_id",
          "operation",
          "status",
          "status_url",
          "created_at"
        ],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "operation": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "succeeded",
              "failed",
              "reconciliation_required"
            ]
          },
          "status_url": {
            "type": "string"
          },
          "resource_type": {
            "type": "string",
            "enum": [
              "post",
              "comment"
            ]
          },
          "publication_id": {
            "type": "string"
          },
          "comment_id": {
            "type": "string"
          },
          "lemmy_post_id": {
            "type": "integer"
          },
          "lemmy_comment_id": {
            "type": "integer"
          },
          "public_url": {
            "type": "string"
          },
          "target": {
            "$ref": "#/components/schemas/Target"
          },
          "score": {
            "type": "integer",
            "enum": [
              -1,
              0,
              1
            ],
            "description": "Final verified vote state. Votes have no public resource URL and no voter list is ever exposed."
          },
          "unchanged": {
            "type": "boolean",
            "description": "The requested vote score was already the account's state; nothing was applied and no quota was consumed."
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "invalid_request",
              "unsupported_content_type",
              "payload_too_large",
              "invalid_api_key",
              "api_key_inactive",
              "missing_scope",
              "organization_inactive",
              "lemmy_authorization_required",
              "lemmy_authorization_unhealthy",
              "entitlement_inactive",
              "quota_exhausted",
              "rate_limited",
              "idempotency_conflict",
              "resource_not_found",
              "post_not_editable",
              "post_not_deletable",
              "comment_not_editable",
              "comment_not_deletable",
              "target_not_found",
              "target_not_allowed",
              "target_locked",
              "invalid_vote_score",
              "upstream_rejected",
              "upstream_unavailable",
              "reconciliation_required",
              "internal_error",
              null
            ],
            "description": "A stable CoinBrowse category. Upstream text is never exposed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Publication": {
        "type": "object",
        "properties": {
          "publication_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "published",
              "deleted",
              "failed"
            ]
          },
          "title": {
            "type": "string"
          },
          "body_markdown": {
            "type": "string"
          },
          "community": {
            "type": "string",
            "description": "Derived from organization type. Never client-supplied."
          },
          "lemmy_post_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "public_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "comment_id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "comment",
              "reply"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "published",
              "deleted",
              "failed"
            ]
          },
          "body_markdown": {
            "type": "string"
          },
          "target": {
            "$ref": "#/components/schemas/Target"
          },
          "lemmy_comment_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "public_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": [
          "entitlement",
          "window",
          "buckets",
          "post_destination",
          "interaction_communities"
        ],
        "properties": {
          "entitlement": {
            "type": "object",
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "paid",
                  "pilot",
                  null
                ],
                "description": "A pilot is explicitly non-paid, time-limited and revocable."
              },
              "plan": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "publisher_api",
                  "publisher_api_pro",
                  "pilot",
                  null
                ]
              },
              "status": {
                "type": "string"
              },
              "starts_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "window": {
            "type": "string",
            "const": "rolling_24h"
          },
          "buckets": {
            "type": "object",
            "description": "Render dynamically. Bucket names are stable; their limits are entitlement configuration and may change without an API change.",
            "properties": {
              "posts": {
                "type": "object",
                "required": [
                  "limit",
                  "used",
                  "reserved",
                  "remaining",
                  "next_slot_at"
                ],
                "properties": {
                  "limit": {
                    "type": "integer",
                    "description": "Configured ceiling for this bucket. Entitlement configuration, not a fixed API constant."
                  },
                  "used": {
                    "type": "integer",
                    "description": "Successful actions inside the rolling window."
                  },
                  "reserved": {
                    "type": "integer",
                    "description": "Accepted but not yet resolved, including writes held pending reconciliation."
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "next_slot_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the oldest counted slot leaves the window. Present only when the bucket is full."
                  }
                }
              },
              "comments_and_replies": {
                "type": "object",
                "required": [
                  "limit",
                  "used",
                  "reserved",
                  "remaining",
                  "next_slot_at"
                ],
                "properties": {
                  "limit": {
                    "type": "integer",
                    "description": "Configured ceiling for this bucket. Entitlement configuration, not a fixed API constant."
                  },
                  "used": {
                    "type": "integer",
                    "description": "Successful actions inside the rolling window."
                  },
                  "reserved": {
                    "type": "integer",
                    "description": "Accepted but not yet resolved, including writes held pending reconciliation."
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "next_slot_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the oldest counted slot leaves the window. Present only when the bucket is full."
                  }
                },
                "description": "Top-level comments and nested replies share this one bucket."
              },
              "vote_state_changes": {
                "type": "object",
                "required": [
                  "limit",
                  "used",
                  "reserved",
                  "remaining",
                  "next_slot_at"
                ],
                "properties": {
                  "limit": {
                    "type": "integer",
                    "description": "Configured ceiling for this bucket. Entitlement configuration, not a fixed API constant."
                  },
                  "used": {
                    "type": "integer",
                    "description": "Successful actions inside the rolling window."
                  },
                  "reserved": {
                    "type": "integer",
                    "description": "Accepted but not yet resolved, including writes held pending reconciliation."
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "next_slot_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the oldest counted slot leaves the window. Present only when the bucket is full."
                  }
                }
              }
            }
          },
          "post_destination": {
            "type": "string"
          },
          "interaction_communities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
