http-relay 0.6.0

A Rust implementation of _some_ of [Http relay spec](https://httprelay.io/).
Documentation
openapi: 3.0.3
info:
  title: pubky-http-relay
  description: |
    An HTTP relay for asynchronous message passing with store-and-forward semantics.

    **Two endpoint groups are available:**
    - `/inbox/{id}` - **Recommended.** Store-and-forward with explicit ACK support
    - `/link/{id}` - **Deprecated.** Synchronous producer/consumer pairing

    ## Inbox API (recommended)

    The inbox API provides at-least-once delivery with explicit acknowledgment:

    1. **Producer** stores a message with `POST /inbox/{id}`
    2. **Consumer** retrieves it with `GET /inbox/{id}`
    3. **Consumer** acknowledges with `DELETE /inbox/{id}`
    4. **Producer** waits for ACK with `GET /inbox/{id}/await` (or polls `/ack`)

    Messages persist for 5 minutes (configurable) until acknowledged. The consumer
    can retry `GET` after connection drops and still receive the message. The
    producer knows delivery succeeded only after the consumer explicitly ACKs.

    This pattern is mobile-friendly: if the app is backgrounded or killed, the
    message remains available for retry, and the producer can detect timeouts.
  version: 0.6.0
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:8080
    description: Local development server

paths:
  /inbox/{id}:
    post:
      summary: Store a message
      description: |
        Store a message in the inbox. Any existing message for this ID is
        overwritten. The message persists for 5 minutes (configurable) or
        until acknowledged via DELETE.

        The producer typically follows this with `GET /inbox/{id}/await` to
        wait for the consumer's acknowledgment.
      operationId: inboxPost
      tags:
        - inbox
      parameters:
        - $ref: '#/components/parameters/inboxId'
      requestBody:
        $ref: '#/components/requestBodies/Message'
      responses:
        '200':
          description: Message stored successfully

    get:
      summary: Retrieve a message (long-poll)
      description: |
        Retrieve a message from the inbox using long-polling. If a message is
        available, returns immediately with the payload. If no message exists,
        waits up to 25 seconds (configurable via --inbox-timeout) for one to
        arrive.

        The message remains in the inbox until explicitly acknowledged with
        DELETE. Multiple GET requests return the same message (at-least-once
        delivery).

        This eliminates the need for client-side polling - the consumer simply
        calls GET and waits.
      operationId: inboxGet
      tags:
        - inbox
      parameters:
        - $ref: '#/components/parameters/inboxId'
      responses:
        '200':
          $ref: '#/components/responses/InboxMessage'
        '408':
          description: No message arrived within timeout (25s default)

    delete:
      summary: Acknowledge a message
      description: |
        Acknowledge receipt of a message, removing it from the inbox.

        This signals to the producer (waiting on `/await`) that the message
        was successfully received and processed.
      operationId: inboxDelete
      tags:
        - inbox
      parameters:
        - $ref: '#/components/parameters/inboxId'
      responses:
        '200':
          description: Message acknowledged and removed
        '404':
          description: No message to acknowledge

  /inbox/{id}/ack:
    get:
      summary: Check ACK status
      description: |
        Check whether a message has been acknowledged.

        Returns "true" if the message was acknowledged (DELETEd),
        "false" if the message exists but hasn't been acknowledged yet,
        or 404 if no message exists (not posted yet, or expired).

        Use this for polling scenarios. For blocking behavior, use `/await`.
      operationId: inboxAckStatus
      tags:
        - inbox
      parameters:
        - $ref: '#/components/parameters/inboxId'
      responses:
        '200':
          description: ACK status
          content:
            text/plain:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              example: 'false'
        '404':
          description: No message exists for this ID

  /inbox/{id}/await:
    get:
      summary: Wait for acknowledgment
      description: |
        Block until the message is acknowledged or timeout is reached.

        The producer calls this after POST to wait for the consumer's DELETE.
        Default timeout is 25 seconds (compatible with proxy timeouts).

        Returns 200 when acknowledged, 408 on timeout.
      operationId: inboxAwait
      tags:
        - inbox
      parameters:
        - $ref: '#/components/parameters/inboxId'
      responses:
        '200':
          description: Message was acknowledged
        '408':
          description: Timeout waiting for acknowledgment (default 25s)

  /link/{id}:
    get:
      summary: Consume a message (deprecated)
      deprecated: true
      description: |
        **Deprecated.** Use `/inbox` endpoints for new integrations.

        Consumer retrieves a message from the channel. Blocks until a producer
        sends data or the timeout (10 minutes) is reached.
      operationId: linkGet
      tags:
        - link
      parameters:
        - $ref: '#/components/parameters/channelId'
      responses:
        '200':
          $ref: '#/components/responses/MessageReceived'
        '408':
          $ref: '#/components/responses/Timeout'

    post:
      summary: Produce a message (deprecated)
      deprecated: true
      description: |
        **Deprecated.** Use `/inbox` endpoints for new integrations.

        Producer sends a message to the channel. Blocks until a consumer
        retrieves the data or the timeout (10 minutes) is reached.
      operationId: linkPost
      tags:
        - link
      parameters:
        - $ref: '#/components/parameters/channelId'
      requestBody:
        $ref: '#/components/requestBodies/Message'
      responses:
        '200':
          $ref: '#/components/responses/MessageDelivered'
        '408':
          $ref: '#/components/responses/Timeout'

components:
  parameters:
    inboxId:
      name: id
      in: path
      required: true
      description: |
        Channel identifier (max 256 bytes). The producer and consumer use the
        same ID to exchange messages.
      schema:
        type: string
        maxLength: 256
      example: my-channel

    channelId:
      name: id
      in: path
      required: true
      description: |
        Unique channel identifier. Producers and consumers using the same
        ID are paired.
      schema:
        type: string
      example: my-channel

  requestBodies:
    Message:
      description: |
        Message payload (max 2KB). Content-Type is preserved and returned
        to the consumer.
      required: true
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
            maxLength: 2048
        application/json:
          schema:
            type: object
          example:
            hello: world
        text/plain:
          schema:
            type: string
          example: Hello, world!

  responses:
    InboxMessage:
      description: Message retrieved from inbox
      headers:
        Content-Type:
          description: Content-Type from the producer's request (if provided)
          schema:
            type: string
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
        application/json:
          schema:
            type: object
        text/plain:
          schema:
            type: string

    MessageReceived:
      description: Message successfully retrieved from producer
      headers:
        Content-Type:
          description: Content-Type from the producer's request (if provided)
          schema:
            type: string
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
        application/json:
          schema:
            type: object
        text/plain:
          schema:
            type: string

    MessageDelivered:
      description: Message successfully delivered to consumer

    Timeout:
      description: Timeout waiting for counterpart (producer or consumer)

tags:
  - name: inbox
    description: |
      Store-and-forward messaging with explicit ACK. Messages persist until
      acknowledged, enabling reliable delivery even when consumers disconnect.
  - name: link
    description: |
      Deprecated relay endpoints (10 min timeout, no caching). Use inbox instead.