openapi: 3.1.0
info:
  title: Bitcoin Payment API Reference
  version: 0.1.0
  license:
    name: Proprietary
  description: >-
    Accept on-chain Bitcoin payments with confirmation tracking, network-scoped
    API keys, and signed webhooks.
servers:
  - url: https://api.manatee-api.io
    description: Production
tags:
  - name: payments
    description: Create and query Bitcoin on-chain payments
  - name: webhooks
    description: Outbound HTTP callbacks sent to your server
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: APIKey
  schemas:
    ErrorResponse:
      type: object
      additionalProperties: false
      properties:
        error:
          type: string
          description: Stable error code
        message:
          type: string
          description: Human-readable details
        request_id:
          type: string
          description: Request ID for support/debugging
      required:
        - error
    CreatePaymentRequest:
      type: object
      additionalProperties: false
      properties:
        amount_sats:
          type: integer
          minimum: 1
          description: Amount in satoshis
          examples:
            - 10000
        underpayment_tolerance_ppm:
          type: integer
          minimum: 0
          maximum: 10000
          default: 0
          description: >-
            Optional downward payment tolerance in parts per million. 1000 ppm
            allows a 0.1% underpayment.
          examples:
            - 0
            - 1000
        webhook_url:
          type: string
          format: uri
          description: >-
            Optional webhook endpoint URL. Production rejects localhost,
            private, link-local, and carrier-grade NAT addresses. The endpoint
            must accept server-to-server HTTP requests; bypass
            Cloudflare/WAF/bot challenges for the webhook path and verify
            requests with the X-Signature HMAC header.
          examples:
            - https://yourapp.com/webhooks/btc
        destination_address:
          type: string
          description: >-
            Optional custom destination address. If omitted, the API derives an
            address from the API key's receive descriptor. If provided, the API
            key must allow customer-provided addresses, the address must match
            the key network, and the same address cannot already have another
            active payment.
        required_confirmations:
          type: integer
          minimum: 1
          maximum: 6
          default: 3
          description: >-
            Number of block confirmations required before the payment is
            confirmed. Defaults to 3.
          examples:
            - 1
            - 3
            - 6
        expires_in:
          type: integer
          minimum: 300
          maximum: 86400
          default: 3600
          description: >-
            Payment expiry in seconds. Only pending payments expire. Defaults to
            3600 (1 hour).
          examples:
            - 900
            - 3600
            - 86400
        reference:
          type: string
          description: Optional external reference ID for reconciliation (e.g. order ID)
          examples:
            - order-123
      required:
        - amount_sats
    PaymentResponse:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          description: Payment ID
        address:
          type: string
          description: Bitcoin receiving address
        txid:
          type: string
          description: Transaction ID (set after detection)
        amount_sats:
          type: integer
          minimum: 1
        underpayment_tolerance_ppm:
          type: integer
          minimum: 0
          maximum: 10000
          description: >-
            Downward payment tolerance in parts per million configured at
            creation time.
        received_sats:
          type: integer
          minimum: 0
        status:
          type: string
          description: Payment status
          enum:
            - pending
            - detected
            - confirmed
            - expired
            - cancelled
          examples:
            - pending
            - detected
            - confirmed
            - expired
            - cancelled
        confirmations:
          type: integer
          minimum: 0
        required_confirmations:
          type: integer
          minimum: 1
          description: Confirmation threshold set at creation
        webhook_url:
          type: string
          format: uri
        network:
          type: string
          description: >-
            Bitcoin network associated with the API key. Hosted testnet/Testnet4
            keys are reported as `testnet`.
          enum:
            - regtest
            - testnet
            - mainnet
          examples:
            - testnet
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: >-
            Expiry timestamp. Only pending payments expire; already detected
            payments continue being tracked.
        reference:
          type: string
          description: External reference ID set at creation
      required:
        - id
        - address
        - amount_sats
        - underpayment_tolerance_ppm
        - status
        - confirmations
        - required_confirmations
        - network
        - created_at
        - expires_at
    PaymentWebhookData:
      type: object
      additionalProperties: false
      properties:
        payment_id:
          type: string
          description: Payment ID
        txid:
          type: string
          description: Bitcoin transaction ID
        address:
          type: string
          description: Bitcoin receiving address
        amount_sats:
          type: integer
          minimum: 1
          description: Expected amount in satoshis
        received_sats:
          type: integer
          minimum: 0
          description: Actually received amount in satoshis
        confirmations:
          type: integer
          minimum: 0
          description: Number of block confirmations at time of event
        webhook_url:
          type: string
          format: uri
      required:
        - payment_id
        - txid
        - address
        - amount_sats
        - received_sats
        - confirmations
    PaginationMeta:
      type: object
      properties:
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
        has_more:
          type: boolean
      required:
        - total
        - limit
        - offset
        - has_more
    ListPaymentsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PaymentResponse'
        pagination:
          $ref: '#/components/schemas/PaginationMeta'
      required:
        - data
        - pagination
    WebhookPayload:
      type: object
      additionalProperties: false
      properties:
        version:
          type: string
          description: Webhook payload schema version
          examples:
            - '1'
        type:
          type: string
          description: Event type
          examples:
            - payment.detected
            - payment.confirmed
          enum:
            - payment.detected
            - payment.confirmed
        data:
          $ref: '#/components/schemas/PaymentWebhookData'
      required:
        - version
        - type
        - data
    TestWebhookRequest:
      type: object
      additionalProperties: false
      properties:
        webhook_url:
          type: string
          format: uri
          description: >-
            Public webhook endpoint URL that should receive the synthetic test
            event. The endpoint must accept server-to-server HTTP requests and
            must not require browser challenges, captchas, or login redirects.
          examples:
            - https://yourapp.com/webhooks/btc
      required:
        - webhook_url
    TestWebhookResponse:
      type: object
      additionalProperties: false
      properties:
        event_id:
          type: string
          description: Unique ID used for this test delivery
        event_type:
          type: string
          enum:
            - payment.confirmed
          description: Synthetic event type sent to the webhook endpoint
        status:
          type: integer
          description: HTTP status returned by the webhook endpoint
          examples:
            - 200
        delivered:
          type: boolean
          description: Whether the webhook endpoint returned a 2xx status
      required:
        - event_id
        - event_type
        - status
        - delivered
    WebhookEventResponse:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          description: Unique event delivery ID
        payment_id:
          type: string
          description: Payment ID this webhook belongs to
        event_type:
          type: string
          enum:
            - payment.detected
            - payment.confirmed
        status:
          type: string
          enum:
            - pending
            - processing
            - delivered
            - failed
            - failed_permanent
        attempt:
          type: integer
          minimum: 0
          description: Number of delivery attempts already made
        webhook_url:
          type: string
          format: uri
        last_error:
          type: string
          description: Last delivery error, if any
        created_at:
          type: string
          format: date-time
        delivered_at:
          type: string
          format: date-time
          description: Set after successful delivery
      required:
        - id
        - payment_id
        - event_type
        - status
        - attempt
        - webhook_url
        - created_at
paths:
  /v1/btc/payments:
    post:
      operationId: createPayment
      tags:
        - payments
      summary: Create a new payment
      description: Creates a payment and returns a Bitcoin address.
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
          description: Idempotency key for safe retries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
      responses:
        '200':
          description: Payment created
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: Plan limit exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples:
                      - plan_limit_exceeded
                  limit:
                    type: string
                    examples:
                      - confirmed_per_month
                      - active_webhooks
                    description: Limit that was exceeded
                  plan:
                    type: string
                  allowed:
                    type: integer
                required:
                  - error
                  - limit
                  - plan
                  - allowed
        '403':
          description: >-
            The API key is not allowed to use customer-provided destination
            addresses
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            Destination address already has another active payment, or a
            duplicate idempotency request is currently in progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
            text/plain:
              schema:
                type: string
                examples:
                  - duplicate request
                  - idempotency key reused with different request body
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    get:
      summary: Get List of payments by time range
      tags:
        - payments
      operationId: listPayments
      x-codeSamples:
        - lang: curl
          label: curl
          source: >
            curl -X GET
            "https://api.manatee-api.io/v1/btc/payments?from=1744675200&to=1744675900&filter=all&limit=50&offset=0"
            \
              -H "Authorization: Bearer YOUR_API_KEY"
      security:
        - bearerAuth: []
      parameters:
        - name: from
          in: query
          required: true
          schema:
            type: integer
            examples:
              - 1744675200
          description: Start of time range
        - name: to
          in: query
          required: true
          schema:
            type: integer
            examples:
              - 1744675900
          description: End of time range
        - name: filter
          in: query
          required: false
          schema:
            type: string
            enum:
              - all
              - verified
              - unverified
            default: all
          description: >
            Filter by confirmation status.

            - `all`: all payments

            - `verified`: only confirmed payments

            - `unverified`: payments not yet confirmed (pending, detected,
            expired, cancelled)
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Maximum number of payments to return
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of payments to skip
      responses:
        '200':
          description: Paginated list of payments
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListPaymentsResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/btc/payments/{id}:
    get:
      operationId: getPaymentById
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl -X GET "https://api.manatee-api.io/v1/btc/payments/abc123" \
              -H "Authorization: Bearer YOUR_API_KEY"
      tags:
        - payments
      summary: Get a payment by ID
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Payment
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/btc/payments/{id}/cancel:
    post:
      operationId: cancelPayment
      x-codeSamples:
        - lang: curl
          label: curl
          source: >
            curl -X POST
            "https://api.manatee-api.io/v1/btc/payments/abc123/cancel" \
              -H "Authorization: Bearer YOUR_API_KEY"
      tags:
        - payments
      summary: Cancel a pending payment
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Payment cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Payment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Payment cannot be cancelled (not in pending state)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed per second
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when the current window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/btc/payments/{id}/webhook-events:
    get:
      operationId: listWebhookEventsForPayment
      tags:
        - webhooks
      summary: List webhook delivery events for a payment
      description: >-
        Returns stored webhook delivery attempts for a payment that belongs to
        the authenticated API key.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Webhook delivery events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookEventResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Payment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/webhooks/test:
    post:
      operationId: sendTestWebhook
      tags:
        - webhooks
      summary: Send a test webhook
      description: >
        Sends a synthetic `payment.confirmed` webhook event to the provided
        `webhook_url`.


        Use this endpoint to verify endpoint reachability, request headers,
        payload parsing,

        and signature validation without creating a Bitcoin transaction. The
        test event is

        delivered immediately and is not stored as a payment or webhook event.


        If the webhook URL is protected by Cloudflare, a WAF, bot protection,
        captchas,

        browser challenges, or login redirects, bypass those protections for the
        webhook

        path and verify authenticity with the `X-Signature` HMAC header instead.
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl -X POST "https://api.manatee-api.io/v1/webhooks/test" \
              -H "Authorization: Bearer YOUR_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"webhook_url":"https://yourapp.com/webhooks/btc"}'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TestWebhookRequest'
      responses:
        '200':
          description: Test webhook delivered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestWebhookResponse'
        '400':
          description: Invalid request or webhook URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Rate limit exceeded.
            - Free and Beta: 180 requests/min
            - Starter: 600 requests/min
            - Basic: 1200 requests/min
            - Pro: 3000 requests/min
            - Business: 6000 requests/min
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Webhook endpoint could not be reached or returned a non-2xx status
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TestWebhookResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
webhooks:
  payment.detected:
    post:
      summary: Payment detected
      tags:
        - webhooks
      security: []
      description: >
        Sent to the `webhook_url` specified during payment creation once an
        incoming

        transaction is detected in the mempool.


        **Signature verification**


        Every request includes an HMAC-SHA256 signature in the `X-Signature`
        header.

        Verify it by computing `HMAC-SHA256(secret, body)` using your webhook
        signing secret and

        comparing the result to the `sha256=<hex>` value in the header.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookPayload'
      parameters:
        - in: header
          name: X-Event-ID
          required: true
          schema:
            type: string
          description: Unique identifier for this event delivery
        - in: header
          name: X-Event-Type
          required: true
          schema:
            type: string
            examples:
              - payment.detected
          description: Event type
        - in: header
          name: X-Signature
          required: true
          schema:
            type: string
            examples:
              - sha256=a3f1...
          description: >-
            HMAC-SHA256 signature of the raw request body using your webhook
            signing secret
      responses:
        '200':
          description: Webhook acknowledged. Any 2xx response is treated as success.
  payment.confirmed:
    post:
      summary: Payment confirmed
      tags:
        - webhooks
      security: []
      description: >
        Sent to the `webhook_url` specified during payment creation once the
        payment

        reaches the required number of confirmations.


        **Signature verification**


        Every request includes an HMAC-SHA256 signature in the `X-Signature`
        header.

        Verify it by computing `HMAC-SHA256(secret, body)` using your webhook
        signing secret and

        comparing the result to the `sha256=<hex>` value in the header.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookPayload'
      parameters:
        - in: header
          name: X-Event-ID
          required: true
          schema:
            type: string
          description: Unique identifier for this event delivery
        - in: header
          name: X-Event-Type
          required: true
          schema:
            type: string
            examples:
              - payment.confirmed
          description: Event type
        - in: header
          name: X-Signature
          required: true
          schema:
            type: string
            examples:
              - sha256=a3f1...
          description: >-
            HMAC-SHA256 signature of the raw request body using your webhook
            signing secret
      responses:
        '200':
          description: Webhook acknowledged. Any 2xx response is treated as success.
