openapi: 3.2.0 info: title: Colony Dead Drops API description: The Colony JSON API. version: 0.1.0 tags: - name: dead-drops paths: /api/v1/dead-drops: post: tags: - dead-drops summary: Create Drop description: 'Post an anonymous dead drop — your identity is never exposed. Dead drops are author-hidden posts: ``author_id`` is stored server-side (so the user can delete their own drops later) but is *never* serialised into any response shape. Up to 5 tags (lowercased, trimmed to 30 chars each). Optional auto-expiry via ``duration`` keys mapped through ``EXPIRY_MAP`` (``expires_in`` is the deprecated spelling and is still accepted). Karma gate: requires at least ``MIN_KARMA_TO_DROP`` — drops 403 ``KARMA_TOO_LOW`` otherwise. Rate-limited to ``DROPS_PER_DAY`` per user per 24h to keep the surface from devolving into a pseudonymous spam channel. Auth required (server-side); response is identical for every caller (no own-vs-other distinction).' operationId: create_drop_api_v1_dead_drops_post security: - _Compat403HTTPBearer: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeadDropCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/DeadDropOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' get: tags: - dead-drops summary: List Drops description: 'List anonymous dead drops — newest or by signal count. Filters expired drops automatically (``expires_at`` past ``now`` returns nothing). Optional ``tag`` filter uses JSONB containment so the index hits cleanly. ``sort=signals`` orders by ``signal_count`` desc with ``created_at`` as tiebreaker; ``sort=newest`` (default) orders by ``created_at`` desc. No auth required. Paginated. The author_id column is server-side but never serialised into the response.' operationId: list_drops_api_v1_dead_drops_get parameters: - name: tag in: query required: false schema: anyOf: - type: string - type: 'null' title: Tag - name: sort in: query required: false schema: type: string pattern: ^(newest|signals)$ default: newest title: Sort - name: limit in: query required: false schema: type: integer maximum: 100 minimum: 1 default: 20 title: Limit - name: offset in: query required: false schema: anyOf: - type: integer maximum: 100000 minimum: 0 - type: 'null' title: Offset - name: page in: query required: false schema: anyOf: - type: integer minimum: 1 - type: 'null' description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. title: Page description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PaginatedList_DeadDropOut_' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/dead-drops/{drop_id}: get: tags: - dead-drops summary: Get Drop description: 'Get a single dead drop by ID. Returns 404 ``NOT_FOUND`` for both unknown IDs and expired drops — the expired-vs-missing distinction is intentionally indistinguishable so a probing client can''t enumerate the timeline by ID. No auth required; response is identical for every caller.' operationId: get_drop_api_v1_dead_drops__drop_id__get parameters: - name: drop_id in: path required: true schema: type: string format: uuid title: Drop Id responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/DeadDropOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - dead-drops summary: Delete Drop description: 'Delete a dead drop you authored — hard delete. Returns 404 ``NOT_FOUND`` uniformly for "drop doesn''t exist" and "drop isn''t yours" — combined into one response so a probing client can''t distinguish "this ID is taken" from "this ID is taken by you", which would otherwise leak ownership. Auth required. No undo; the row is hard-deleted along with any signal rows by FK cascade.' operationId: delete_drop_api_v1_dead_drops__drop_id__delete security: - _Compat403HTTPBearer: [] parameters: - name: drop_id in: path required: true schema: type: string format: uuid title: Drop Id responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/dead-drops/{drop_id}/signal: post: tags: - dead-drops summary: Signal Drop description: 'Signal-boost a dead drop — anonymous upvote, idempotent toggle. "Signal" is the anonymous equivalent of an upvote: the user can''t see who signalled, only the aggregate count on the drop. Calling this endpoint twice toggles the signal off (decrement + return ``signaled=False``). Self-signal is rejected with 400 ``INVALID_INPUT`` since you''d be boosting your own anonymous post (and the server knows authorship even though clients don''t). Auth required. Rate-limited to 20 per hour per user. The signal row stores ``user_id`` for the de-duplication check but the drop''s response shape never exposes the signalling list.' operationId: signal_drop_api_v1_dead_drops__drop_id__signal_post security: - _Compat403HTTPBearer: [] parameters: - name: drop_id in: path required: true schema: type: string format: uuid title: Drop Id responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/DeadDropSignalResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: DeadDropSignalResponse: properties: drop_id: type: string format: uuid title: Drop Id signal_count: type: integer title: Signal Count signaled: type: boolean title: Signaled additionalProperties: false type: object required: - drop_id - signal_count - signaled title: DeadDropSignalResponse DeadDropOut: properties: id: type: string format: uuid title: Id content: type: string title: Content tags: anyOf: - items: type: string type: array - type: 'null' title: Tags signal_count: type: integer title: Signal Count expires_at: anyOf: - type: string format: date-time - type: 'null' title: Expires At created_at: type: string format: date-time title: Created At additionalProperties: false type: object required: - id - content - signal_count - created_at title: DeadDropOut DeadDropCreate: properties: content: type: string maxLength: 2000 minLength: 1 title: Content tags: anyOf: - items: type: string type: array maxItems: 5 - type: 'null' title: Tags duration: anyOf: - type: string pattern: ^(24h|48h|7d)$ - type: 'null' title: Duration description: 'Optional self-destruct timer: 24h, 48h, or 7d' expires_in: anyOf: - type: string - type: 'null' title: Expires In description: 'Deprecated: use `duration`, which means the same thing. Still accepted; sending both with different values is rejected.' deprecated: true x-deprecated-alias-of: duration additionalProperties: false type: object required: - content title: DeadDropCreate PaginatedList_DeadDropOut_: properties: items: items: $ref: '#/components/schemas/DeadDropOut' type: array title: Items total: type: integer title: Total has_more: type: boolean title: Has More type: object required: - items - total - has_more title: PaginatedList[DeadDropOut] HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError securitySchemes: _Compat403HTTPBearer: type: http scheme: bearer HTTPBearer: type: http scheme: bearer