openapi: 3.0.3
info:
  title: Kvasyr API
  version: 0.2.10
  description: |
    Base path for client API is /v1. /api is a temporary alias.
  license:
    name: Proprietary
    url: https://github.com/rmrk-team/kvasyr/blob/main/LICENSE
servers:
- url: /
security:
- BearerAuth: []
paths:
  /admin/api-keys:
    post:
      summary: Create API key
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_create_api_key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              properties:
                user_id:
                  type: integer
                name:
                  type: string
                  maxLength: 120
                admin_mode:
                  type: boolean
      responses:
        '200':
          description: API key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    type: string
                  name:
                    type: string
                  key_prefix:
                    type: string
                  admin_mode:
                    type: boolean
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/backfill:
    get:
      summary: List backfill requests
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_list_backfill_requests
      responses:
        '200':
          description: Backfill requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BackfillListResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/backfill/{id}:
    patch:
      summary: Admin Update Backfill
      operationId: admin_update_backfill
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      summary: Admin Delete Backfill
      operationId: admin_delete_backfill
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: Deleted
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/backfill/{id}/cancel:
    post:
      summary: Cancel backfill request
      security:
      - AdminPassword: []
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: admin_cancel_backfill
      responses:
        '200':
          description: Backfill request cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/deliveries:
    get:
      summary: List deliveries
      security:
      - AdminPassword: []
      - BearerAuth: []
      parameters:
      - name: status
        in: query
        schema:
          type: string
      - name: cursor
        in: query
        schema:
          type: string
        description: Opaque base64url cursor from previous response
      - name: limit
        in: query
        schema:
          type: integer
      operationId: admin_list_deliveries
      responses:
        '200':
          description: Delivery list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryListResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/deliveries/{id}:
    delete:
      summary: Delete delivery
      security:
      - AdminPassword: []
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: admin_delete_delivery
      responses:
        '200':
          description: Delivery deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/plans:
    get:
      summary: List plans
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_list_plans
      responses:
        '200':
          description: Plan list
          content:
            application/json:
              schema:
                type: object
                properties:
                  plans:
                    type: array
                    items:
                      $ref: '#/components/schemas/Plan'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      summary: Create plan
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_create_plan
      responses:
        '200':
          description: Plan created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminCreateResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/subscriptions/{id}:
    delete:
      summary: Delete subscription
      security:
      - AdminPassword: []
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: admin_delete_subscription
      responses:
        '200':
          description: Subscription deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/subscriptions/{id}/disable:
    post:
      summary: Admin Disable Subscription
      operationId: admin_disable_subscription
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/tracked-contracts:
    get:
      summary: List tracked contracts
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_list_tracked_contracts
      responses:
        '200':
          description: Tracked contracts
          content:
            application/json:
              schema:
                type: object
                properties:
                  tracked_contracts:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        chain_id:
                          type: integer
                        contract_address:
                          type: string
                        label:
                          type: string
                          nullable: true
                        enabled:
                          type: boolean
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      summary: Add tracked contract
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_add_tracked_contract
      responses:
        '200':
          description: Tracked contract added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminCreateResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/tracked-contracts/{id}:
    delete:
      summary: Admin Delete Tracked Contract
      operationId: admin_delete_tracked_contract
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: Deleted
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/tracked-contracts/{id}/disable:
    post:
      summary: Admin Disable Tracked Contract
      operationId: admin_disable_tracked_contract
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/tracked-contracts/{id}/enable:
    post:
      summary: Admin Enable Tracked Contract
      operationId: admin_enable_tracked_contract
      tags:
      - admin
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - AdminPassword: []
      - BearerAuth: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/users:
    post:
      summary: Create user
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_create_user
      responses:
        '200':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminCreateResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /admin/users/plan:
    post:
      summary: Assign user plan
      security:
      - AdminPassword: []
      - BearerAuth: []
      operationId: admin_assign_user_plan
      responses:
        '200':
          description: Plan assigned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /healthz:
    get:
      summary: Health check
      operationId: healthz
      responses:
        '200':
          description: OK
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /metrics:
    get:
      summary: Prometheus metrics
      operationId: metrics_handler
      responses:
        '200':
          description: Metrics text
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /openapi.yaml:
    get:
      summary: Openapi Yaml
      operationId: openapi_yaml
      tags:
      - public
      security: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/backfill:
    post:
      summary: Request backfill
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BackfillRequest'
      operationId: request_backfill
      responses:
        '200':
          description: Backfill queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BackfillResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/backfill/{id}/cancel:
    post:
      summary: Cancel Backfill
      operationId: cancel_backfill
      tags:
      - api
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      security:
      - BearerAuth: []
      responses:
        '200':
          description: OK
        default:
          description: Error
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/deliveries:
    get:
      summary: List deliveries
      parameters:
      - name: status
        in: query
        schema:
          type: string
      - name: cursor
        in: query
        schema:
          type: string
        description: Opaque base64url cursor from previous response
      - name: limit
        in: query
        schema:
          type: integer
      operationId: list_deliveries
      responses:
        '200':
          description: Delivery list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryListResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/events:
    get:
      summary: List events
      parameters:
      - name: subscription_id
        in: query
        required: true
        schema:
          type: integer
      - name: after
        in: query
        schema:
          type: string
        description: RFC3339 timestamp (deprecated, use cursor)
        deprecated: true
      - name: cursor
        in: query
        schema:
          type: string
        description: Opaque base64url cursor from previous response
      - name: limit
        in: query
        schema:
          type: integer
      operationId: list_events
      responses:
        '200':
          description: Event list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventsResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/me:
    get:
      summary: Get current user details
      operationId: get_me
      responses:
        '200':
          description: User info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/subscriptions:
    get:
      summary: List subscriptions
      operationId: list_subscriptions
      responses:
        '200':
          description: Subscriptions list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionListResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      summary: Create subscription
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSubscriptionRequest'
      operationId: create_subscription
      responses:
        '200':
          description: Subscription created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSubscriptionResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/subscriptions/{id}:
    get:
      summary: Get subscription
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: get_subscription
      responses:
        '200':
          description: Subscription detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      summary: Update subscription
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionRequest'
      operationId: update_subscription
      responses:
        '200':
          description: Subscription updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      summary: Delete subscription
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: delete_subscription
      responses:
        '200':
          description: Subscription deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/subscriptions/{id}/rotate-secret:
    post:
      summary: Rotate webhook secret
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: rotate_subscription_secret
      responses:
        '200':
          description: Secret rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotateSecretResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/subscriptions/{id}/verify-webhook:
    post:
      summary: Verify webhook endpoint
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
      operationId: verify_webhook
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookVerifyResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /v1/webhook-test:
    post:
      summary: Send a signed webhook test
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookTestRequest'
      operationId: webhook_test
      responses:
        '200':
          description: Test delivered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookTestResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: APIKey
    AdminPassword:
      type: apiKey
      in: header
      name: X-Admin-Password
  schemas:
    WebhookRequest:
      type: object
      properties:
        url:
          type: string
        enabled:
          type: boolean
        secret:
          type: string
    StoredRequest:
      type: object
      properties:
        enabled:
          type: boolean
    FiltersRequest:
      type: object
      properties:
        topic1_in:
          type: array
          items:
            type: string
        topic2_in:
          type: array
          items:
            type: string
        topic3_in:
          type: array
          items:
            type: string
        topic1_not_in:
          type: array
          items:
            type: string
        topic2_not_in:
          type: array
          items:
            type: string
        topic3_not_in:
          type: array
          items:
            type: string
        from:
          type: string
        to:
          type: string
    ContractScope:
      type: object
      required:
      - type
      properties:
        type:
          type: string
          enum:
          - single
          - many
          - wildcard
        addresses:
          type: array
          items:
            type: string
    CreateSubscriptionRequest:
      type: object
      required:
      - chain_id
      - contract_scope
      - event_signature
      properties:
        chain_id:
          type: integer
        contract_scope:
          $ref: '#/components/schemas/ContractScope'
        event_signature:
          type: string
        filters:
          $ref: '#/components/schemas/FiltersRequest'
        webhook:
          $ref: '#/components/schemas/WebhookRequest'
        stored:
          $ref: '#/components/schemas/StoredRequest'
    UpdateSubscriptionRequest:
      type: object
      properties:
        webhook:
          type: object
          properties:
            url:
              type: string
            enabled:
              type: boolean
            secret:
              type: string
        stored:
          $ref: '#/components/schemas/StoredRequest'
        filters:
          $ref: '#/components/schemas/FiltersRequest'
    BackfillRequest:
      type: object
      required:
      - subscription_id
      properties:
        subscription_id:
          type: integer
        from_block:
          type: integer
        to_block:
          type: integer
    WebhookTestRequest:
      type: object
      required:
      - subscription_id
      properties:
        subscription_id:
          type: integer
        url:
          type: string
    ErrorResponse:
      type: object
      required:
      - error
      - code
      properties:
        error:
          type: string
        code:
          type: string
    WebhookResponse:
      type: object
      properties:
        url:
          type: string
          nullable: true
        enabled:
          type: boolean
        secret:
          type: string
          nullable: true
        verified_at:
          type: string
          nullable: true
    Plan:
      type: object
      properties:
        id:
          type: integer
        slug:
          type: string
        name:
          type: string
        monthly_credits:
          type: integer
        allow_wildcards:
          type: boolean
        enable_webhook:
          type: boolean
        enable_stored:
          type: boolean
        max_subscriptions:
          type: integer
        retention_days:
          type: integer
        webhook_max_attempts:
          type: integer
          nullable: true
        webhook_rate_limit_per_minute:
          type: integer
          nullable: true
        webhook_subscription_rate_limit_per_minute:
          type: integer
          nullable: true
    Subscription:
      type: object
      properties:
        id:
          type: integer
        chain_id:
          type: integer
        event_signature:
          type: string
        contract_scope:
          $ref: '#/components/schemas/ContractScope'
        webhook:
          $ref: '#/components/schemas/WebhookResponse'
        stored_enabled:
          type: boolean
        paused_reason:
          type: string
          nullable: true
    CreateSubscriptionResponse:
      type: object
      properties:
        id:
          type: integer
        chain_id:
          type: integer
        event_signature:
          type: string
        webhook:
          $ref: '#/components/schemas/WebhookResponse'
        stored_enabled:
          type: boolean
    SubscriptionListResponse:
      type: object
      properties:
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
    MeResponse:
      type: object
      properties:
        id:
          type: integer
        email:
          type: string
          nullable: true
        credits_remaining:
          type: integer
        effective_credits_remaining:
          type: integer
        credits_reset_at:
          type: string
          nullable: true
        subscription_count:
          type: integer
        plan:
          $ref: '#/components/schemas/Plan'
    EventPayload:
      type: object
      properties:
        id:
          type: string
        chain_id:
          type: integer
        chain_name:
          type: string
        contract_address:
          type: string
        event_signature:
          type: string
        block_number:
          type: integer
        block_hash:
          type: string
        tx_hash:
          type: string
        log_index:
          type: integer
        timestamp:
          type: integer
          nullable: true
        finality:
          type: object
          properties:
            mode:
              type: string
            depth_fallback:
              type: integer
            finalized:
              type: boolean
        decoded:
          type: object
          nullable: true
        topics:
          type: array
          items:
            type: string
        data:
          type: string
    EventsResponse:
      type: object
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/EventPayload'
        next_cursor:
          type: string
          nullable: true
        has_more:
          type: boolean
    Delivery:
      type: object
      properties:
        id:
          type: integer
        subscription_match_id:
          type: integer
        status:
          type: string
        attempts:
          type: integer
        next_retry_at:
          type: string
          nullable: true
        last_error:
          type: string
          nullable: true
        delivered_at:
          type: string
          nullable: true
    DeliveryListResponse:
      type: object
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/Delivery'
        next_cursor:
          type: string
          nullable: true
        has_more:
          type: boolean
    BackfillResponse:
      type: object
      properties:
        id:
          type: integer
        status:
          type: string
        note:
          type: string
    BackfillRequestStatus:
      type: object
      properties:
        id:
          type: integer
        user_id:
          type: integer
        subscription_id:
          type: integer
        from_block:
          type: integer
          nullable: true
        to_block:
          type: integer
          nullable: true
        status:
          type: string
        created_at:
          type: string
        note:
          type: string
          nullable: true
    BackfillListResponse:
      type: object
      properties:
        backfill_requests:
          type: array
          items:
            $ref: '#/components/schemas/BackfillRequestStatus'
    AdminCreateResponse:
      type: object
      properties:
        id:
          type: integer
    OkResponse:
      type: object
      properties:
        ok:
          type: boolean
    RotateSecretResponse:
      type: object
      properties:
        secret:
          type: string
    WebhookTestResponse:
      type: object
      properties:
        ok:
          type: boolean
        status:
          type: integer
        response_time_ms:
          type: integer
        body_snippet:
          type: string
        body_truncated:
          type: boolean
    WebhookVerifyResponse:
      type: object
      properties:
        ok:
          type: boolean
        status:
          type: integer
        reason:
          type: string
          nullable: true
        response_time_ms:
          type: integer
        verified_at:
          type: string
          nullable: true
  responses:
    ErrorResponse:
      description: Error response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
x-generated-by: cargo run --bin generate-openapi
