> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reflecto.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Send via capability URL (path-token alias)

> Convenience alias for `POST /v1/send` where the bearer token is the last path segment instead of the `Authorization` header. Useful in environments that cannot set custom headers (basic IoT devices, simple shell pipelines).

Path-token requests have their URL redacted before any access log write. The header form is still the canonical/preferred way to call the API — path-tokens are easier to leak via shell history, screenshots, and referrers.



## OpenAPI

````yaml /openapi.yaml post /v1/send/{token}
openapi: 3.1.0
info:
  title: Reflecto API
  version: 1.1.0
  description: >-
    The Reflecto public HTTP API lets you push notifications to your own paired

    devices — your Android phone and any browser extensions you've paired with
    it.

    Designed for the same job as Pushover or the old Pushbullet API: a single

    HTTPS call from a script, cron job, or webhook puts a notification on every

    screen you own.


    Authentication uses opaque prefixed bearer tokens (`rfk_live_…`) created and

    managed on your paired Android device — there are no email accounts, no

    passwords, no server-side user records beyond device pairing state. See the

    Authentication guide for the token lifecycle.


    The public-API surface is small on purpose: `POST /v1/send`, its

    capability-URL alias `POST /v1/send/{token}`, and a Pushover-compatible shim

    at `POST /v1/messages.json`. Mirroring traffic from your phone to your

    extension never reaches this surface — it stays end-to-end encrypted via the

    private `/v1/sync` SSE stream.


    See the Guides tab for the quickstart, authentication model, rate-limit

    contract, and encryption posture.
  contact:
    name: Reflecto
    url: https://github.com/reflectoapp/reflecto
  license:
    name: MIT
    url: https://github.com/reflectoapp/reflecto/blob/main/LICENSE
servers:
  - url: https://api.reflecto.dev
    description: Production
security:
  - BearerAuth: []
tags:
  - name: Send
    description: Push notifications to one or all of your paired devices.
  - name: Pushover compatibility
    description: >-
      Drop-in replacement for `POST https://api.pushover.net/1/messages.json`.
      Field names, request shape, and response shape mirror Pushover's so
      existing senders can migrate by changing a single URL. See [Pushover
      compatibility guide](/guides/pushover-compatibility) for the full mapping.
paths:
  /v1/send/{token}:
    post:
      tags:
        - Send
      summary: Send via capability URL (path-token alias)
      description: >-
        Convenience alias for `POST /v1/send` where the bearer token is the last
        path segment instead of the `Authorization` header. Useful in
        environments that cannot set custom headers (basic IoT devices, simple
        shell pipelines).


        Path-token requests have their URL redacted before any access log write.
        The header form is still the canonical/preferred way to call the API —
        path-tokens are easier to leak via shell history, screenshots, and
        referrers.
      operationId: sendCapability
      parameters:
        - schema:
            type: string
            pattern: ^rfk_(live|test)_[A-Za-z0-9]{32}$
            description: Reflecto sender token (`rfk_live_…` or `rfk_test_…`).
            example: rfk_live_aB3xQ7mN9pK2vR5tY8uW4sZ1cE6dF0gH
          required: true
          description: Reflecto sender token (`rfk_live_…` or `rfk_test_…`).
          name: token
          in: path
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/SendRequest'
          text/plain:
            schema:
              type: string
              example: Backup finished in 12m
          application/octet-stream:
            schema:
              type: string
              description: >-
                Treated as `text/plain` — convenient for shell pipelines that
                default to `application/octet-stream`. Matches `/v1/send`.
      responses:
        '200':
          description: Accepted and enqueued.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
            X-RateLimit-Resource:
              $ref: '#/components/headers/XRateLimitResource'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      security: []
components:
  schemas:
    SendRequest:
      type: object
      properties:
        message:
          type: string
          minLength: 1
          description: >-
            Notification body. Required. 1–1500 bytes (UTF-8). `maxLength` is
            omitted from the schema because JSON Schema measures characters, not
            bytes — server enforces a byte cap to keep the 2 KB envelope budget
            honest across CJK / emoji content.
          example: Backup finished in 12m
        title:
          type: string
          minLength: 1
          description: >-
            Headline rendered above the body. 1–100 bytes (UTF-8). Schema omits
            `maxLength` because JSON Schema measures characters, not bytes.
          example: Backup
        priority:
          type: string
          enum:
            - min
            - low
            - default
            - high
            - urgent
          default: default
          description: Notification priority. `min`/`low` use normal FCM; rest use high.
          example: default
        tags:
          type: array
          items:
            type: string
            description: >-
              Tag string. Oversize / overcount entries are truncated with a
              warning rather than rejected.
            example: ops
          description: >-
            Up to 5 tags rendered as labels. Oversize / overcount entries are
            truncated with a warning rather than rejected — `maxItems` is
            deliberately omitted so SDK generators don't client-side-reject
            valid-but-overcount inputs that the server will accept and trim.
        url:
          type: string
          format: uri
          description: >-
            Clickable URL attached to the notification. http(s) only, ≤ 512
            bytes.
          example: https://example.com/backup/123
        url_title:
          type: string
          description: >-
            Display text for `url`. ≤ 32 bytes (UTF-8). Schema omits `maxLength`
            because JSON Schema measures characters, not bytes.
          example: View report
        actions:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
                minLength: 1
                example: Acknowledge
              url:
                type: string
                format: uri
                description: >-
                  http(s) only — `javascript:` and `data:` URLs are rejected
                  with `400 invalid_action`. ≤ 512 bytes.
                example: https://example.com/ack
            required:
              - label
              - url
          description: >-
            Up to 3 action buttons. Each button opens its `url` when tapped.
            Overcount entries are truncated with a warning — `maxItems` is
            deliberately omitted so SDK generators don't client-side-reject.
        device:
          type: string
          maxLength: 256
          default: all
          description: >-
            Target device(s). `all` (default), `mobile` (alias `phone`),
            `desktop`, or a comma-separated list of device labels. Unknown
            labels become warnings.
          example: all
        markdown:
          type: boolean
          default: false
          description: >-
            Render the body as Markdown on the extension. Ignored on Android in
            MVP.
          example: false
        ttl:
          type: integer
          description: >-
            Seconds the message stays in the per-device queue if not delivered
            live. Out-of-range values are clamped to the [0, 259200] range (72
            hours, matching the Redis queue TTL) and a warning is returned.
          example: 259200
          default: 259200
          minimum: 0
          maximum: 259200
      required:
        - message
      description: >-
        Canonical send payload. Form-encoded and text/plain bodies are
        documented in the guides — the server normalises them to this shape.
    SendResponse:
      type: object
      properties:
        id:
          type: string
          example: msg_3f6e2a7c9b1d4f8e8a5b6c1d2e3f4a5b
        delivered_to:
          type: array
          items:
            type: object
            properties:
              device_id:
                type: string
                example: dev_9af3
              type:
                type: string
                enum:
                  - android
                  - extension
                example: android
            required:
              - device_id
              - type
        warnings:
          type: array
          items:
            type: string
      required:
        - id
        - delivered_to
        - warnings
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              example: message_too_long
            message:
              type: string
              example: message must be ≤ 1500 bytes
            details:
              type: object
              additionalProperties: {}
          required:
            - code
            - message
      required:
        - error
    SimpleErrorResponse:
      type: object
      properties:
        error:
          type: string
          example: invalid_token
        message:
          type: string
      required:
        - error
      description: >-
        Flat error shape used for auth failures — `invalid_token` carries only
        the stable code; `missing_token` adds a `message` hint pointing at the
        `Authorization: Bearer …` header. The `message` field is therefore
        optional.
  headers:
    XRateLimitLimit:
      description: Cap for the binding rate-limit layer on this request.
      schema:
        type: integer
        example: 60
    XRateLimitRemaining:
      description: Remaining sends in the binding layer's current window.
      schema:
        type: integer
        example: 47
    XRateLimitReset:
      description: Unix epoch (seconds) when the binding layer's window resets.
      schema:
        type: integer
        example: 1234567890
    XRateLimitResource:
      description: >-
        Names which layer was tightest on this request. The enumerated values
        match the resources tracked in
        `apps/server/src/middleware/publicApiLimits.ts`.
      schema:
        type: string
        enum:
          - token_burst
          - token_monthly
          - receiver_daily
          - ip_minute
          - ip_hour
        example: token_burst
  responses:
    BadRequest:
      description: Malformed payload or invalid field value.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalidPriority:
              value:
                error:
                  code: invalid_priority
                  message: priority must be one of min, low, default, high, urgent
            messageTooLong:
              value:
                error:
                  code: message_too_long
                  message: message must be ≤ 1500 bytes
                  details:
                    bytes: 1620
                    max: 1500
    Unauthorized:
      description: Missing, invalid, or revoked bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleErrorResponse'
          examples:
            missingToken:
              value:
                error: missing_token
                message: 'Authorization: Bearer rfk_live_… required'
            invalidToken:
              value:
                error: invalid_token
    Forbidden:
      description: Token exists but lacks scope for the requested action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            priorityCapped:
              value:
                error:
                  code: priority_capped
                  message: Token's priority_cap is 'default'; requested 'high'
    PayloadTooLarge:
      description: Serialized envelope exceeds the 2048-byte cap.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            payloadTooLarge:
              value:
                error:
                  code: payload_too_large
                  message: Payload exceeds 2048 byte limit
                  details:
                    size: 3104
                    max: 2048
    RateLimited:
      description: Rate limit hit on one of the per-token / per-IP / per-receiver layers.
      headers:
        Retry-After:
          description: >-
            Seconds to wait before retrying. Respect — immediate retries trip
            the same gate.
          schema:
            type: integer
            example: 30
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
        X-RateLimit-Resource:
          $ref: '#/components/headers/XRateLimitResource'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            rateLimitExceeded:
              value:
                error:
                  code: rate_limit_exceeded
                  message: Rate limit hit on token_burst
    ServerError:
      description: Unhandled server error. Retry with exponential backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            internalError:
              value:
                error:
                  code: internal_error
                  message: Unhandled server error. Retry with exponential backoff.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: rfk_live_…
      description: >-
        Opaque bearer token created on your paired Android phone. The format is
        `rfk_live_` followed by 32 URL-safe alphanumeric characters (≥160 bits
        of entropy). Test tokens use the `rfk_test_` prefix.

````