openapi: '3.1.1'
info:
  title: Stendly Merchant API
  description: 'Public Merchant API contract. Authenticate with an Authorization: Bearer st_live_... API key.'
  version: 1.0.0
servers:
  - url: https://api.stendly.com
    description: Mainnet
  - url: https://api-devnet.stendly.com
    description: Devnet
paths:
  /api/b2b/merchants/me:
    get:
      tags:
        - B2B Dashboard
      operationId: GetMerchantProfile
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantProfileResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                description: Standardized API error envelope.
                $ref: '#/components/schemas/ApiErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
  /api/b2b/merchants/stats:
    get:
      tags:
        - B2B Dashboard
      operationId: GetMerchantStats
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantStatsResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
  /api/b2b/merchants/webhook:
    patch:
      tags:
        - B2B Dashboard
      operationId: UpdateMerchantWebhook
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequest'
        required: true
      responses:
        '204':
          description: No Content
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      callbacks:
        merchantWebhook:
          '{$request.body#/webhookUrl}':
            post:
              summary: Receive payment intent event
              description: Stendly sends this POST request to the merchant webhook URL configured with PATCH /api/b2b/merchants/webhook.
              parameters:
                - name: X-Stendly-Signature
                  in: header
                  description: 'HMAC-SHA256 signature over timestamp + raw JSON payload. Format: t={unix_timestamp},v1={hex_digest}.'
                  required: true
                  schema:
                    type: string
                    example: 't=1714567890,v1=3f4c2d8a9b0e1f2a3c4d5e6f7081920a'
                - name: X-Stendly-Idempotency-Key
                  in: header
                  description: Unique delivery idempotency key for this webhook attempt.
                  required: true
                  schema:
                    type: string
                    format: uuid
              requestBody:
                content:
                  application/json:
                    schema:
                      description: Outgoing webhook payload sent by Stendly to the merchant webhook URL.
                      $ref: '#/components/schemas/MerchantWebhookPayload'
                    example:
                      event: payment_intent.succeeded
                      data:
                        paymentIntentId: 8c8b2f6b-16b3-4761-a5f0-8b8c9c5f1a11
                        orderId: order_1001
                        amountCents: 1299
                        expectedAmountCents: 1299
                        txSignature: 5S5kY8uJp9...
                required: true
              responses:
                2XX:
                  description: Webhook accepted by merchant endpoint.
      security:
        - { }
  /api/b2b/merchants/terminals:
    get:
      tags:
        - B2B Dashboard
      operationId: ListMerchantTerminals
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TerminalResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
    post:
      tags:
        - B2B Dashboard
      operationId: CreateMerchantTerminal
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTerminalRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TerminalResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
  /api/merchants/intents:
    post:
      tags:
        - Merchants
      operationId: CreatePaymentIntent
      parameters:
        - name: Idempotency-Key
          in: header
          description: UUID used to make intent creation safe to retry. SDKs generate one automatically.
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentIntentRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntentResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
  '/api/merchants/intents/{intentId}':
    get:
      tags:
        - Merchants
      operationId: GetPaymentIntent
      parameters:
        - name: intentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntentResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - { }
components:
  schemas:
    ApiError:
      type: object
      properties:
        code:
          type:
            - 'null'
            - string
        message:
          type:
            - 'null'
            - string
        traceId:
          type:
            - 'null'
            - string
      additionalProperties: false
    ApiErrorResponse:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ApiError'
      additionalProperties: false
      description: Standardized API error envelope.
    CreatePaymentIntentRequest:
      required:
        - orderId
      type: object
      properties:
        amountCents:
          maximum: 9.223372036854776E+18
          minimum: 1
          type: integer
          format: int64
        orderId:
          maxLength: 128
          minLength: 1
          type: string
        terminalId:
          type:
            - 'null'
            - string
          format: uuid
      additionalProperties: false
      example:
        amountCents: 1299
        orderId: order_1001
        terminalId: f47ac10b-58cc-4372-a567-0e02b2c3d479
    CreateTerminalRequest:
      type: object
      properties:
        name:
          maxLength: 128
          minLength: 1
          type:
            - 'null'
            - string
      additionalProperties: false
    MerchantDailyStatsResponse:
      type: object
      properties:
        date:
          type:
            - 'null'
            - string
        volumeCents:
          type: integer
          format: int64
        transactions:
          type: integer
          format: int32
      additionalProperties: false
    MerchantProfileResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type:
            - 'null'
            - string
        payoutAddress:
          type:
            - 'null'
            - string
        webhookUrl:
          type:
            - 'null'
            - string
        webhookSecret:
          type:
            - 'null'
            - string
        rawApiKey:
          type:
            - 'null'
            - string
        verificationStatus:
          type: integer
          format: int32
      additionalProperties: false
    MerchantStatsResponse:
      type: object
      properties:
        totalVolumeCents:
          type: integer
          format: int64
        totalTransactions:
          type: integer
          format: int32
        successfulTransactions:
          type: integer
          format: int32
        chartData:
          type:
            - 'null'
            - array
          items:
            $ref: '#/components/schemas/MerchantDailyStatsResponse'
      additionalProperties: false
    MerchantWebhookPayload:
      type: object
      properties:
        event:
          type:
            - 'null'
            - string
        data:
          $ref: '#/components/schemas/WebhookData'
      additionalProperties: false
      description: Outgoing webhook payload sent by Stendly to the merchant webhook URL.
    PaymentIntentResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        orderId:
          type:
            - 'null'
            - string
        expectedAmountCents:
          type: integer
          format: int64
        referenceAddress:
          type:
            - 'null'
            - string
        destinationAddress:
          type:
            - 'null'
            - string
        status:
          type:
            - 'null'
            - string
        expiresAt:
          type: string
          format: date-time
      additionalProperties: false
      example:
        id: 8c8b2f6b-16b3-4761-a5f0-8b8c9c5f1a11
        orderId: order_1001
        expectedAmountCents: 1299
        referenceAddress: GvHeM7QbJ...
        destinationAddress: 9xQeWvG816bUx9EP...
        status: pending
        expiresAt: '2026-06-17T12:30:00Z'
    TerminalResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type:
            - 'null'
            - string
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
      additionalProperties: false
    UpdateWebhookRequest:
      required:
        - webhookUrl
      type: object
      properties:
        webhookUrl:
          maxLength: 2048
          minLength: 1
          type: string
          format: uri
      additionalProperties: false
      example:
        webhookUrl: https://merchant.example.com/stendly/webhook
    WebhookData:
      type: object
      properties:
        paymentIntentId:
          type: string
          format: uuid
        orderId:
          type:
            - 'null'
            - string
        amountCents:
          type: integer
          format: int64
        expectedAmountCents:
          type: integer
          format: int64
        txSignature:
          type:
            - 'null'
            - string
      additionalProperties: false
  responses:
    ApiError:
      description: Standard API error response.
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                $ref: '#/components/schemas/ApiError'
            additionalProperties: false
            description: Standardized API error envelope.
          example:
            error:
              code: BAD_REQUEST
              message: Amount must be greater than 0
              traceId: 0HMSAMPLETRACE
  securitySchemes:
    MerchantApiKey:
      type: http
      description: 'Merchant API key in the Authorization header. Format: Bearer st_live_...'
      scheme: bearer
      bearerFormat: st_live_...
  callbacks:
    MerchantWebhook:
      '{$request.body#/webhookUrl}':
        post:
          summary: Receive payment intent event
          description: Stendly sends this POST request to the merchant webhook URL configured with PATCH /api/b2b/merchants/webhook.
          parameters:
            - name: X-Stendly-Signature
              in: header
              description: 'HMAC-SHA256 signature over timestamp + raw JSON payload. Format: t={unix_timestamp},v1={hex_digest}.'
              required: true
              schema:
                type: string
                example: 't=1714567890,v1=3f4c2d8a9b0e1f2a3c4d5e6f7081920a'
            - name: X-Stendly-Idempotency-Key
              in: header
              description: Unique delivery idempotency key for this webhook attempt.
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            content:
              application/json:
                schema:
                  description: Outgoing webhook payload sent by Stendly to the merchant webhook URL.
                  $ref: '#/components/schemas/MerchantWebhookPayload'
                example:
                  event: payment_intent.succeeded
                  data:
                    paymentIntentId: 8c8b2f6b-16b3-4761-a5f0-8b8c9c5f1a11
                    orderId: order_1001
                    amountCents: 1299
                    expectedAmountCents: 1299
                    txSignature: 5S5kY8uJp9...
            required: true
          responses:
            2XX:
              description: Webhook accepted by merchant endpoint.
tags:
  - name: B2B Dashboard
  - name: Merchants