openapi: 3.1.0
info:
  title: DuitDigital Central API
  version: 1.0.0
servers:
  - url: https://api.duitdigital.id
paths:
  /health:
    get:
      operationId: healthLive
      responses: {'200': {description: Application is alive}}
  /health/ready:
    get:
      operationId: healthReady
      responses: {'200': {description: Dependencies are ready}, '503': {description: Dependency unavailable}}
  /v1/auth/register:
    post:
      tags: [Auth]
      requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/RegisterRequest'}}}}
      responses: {'201': {description: Registered}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/auth/login:
    post:
      tags: [Auth]
      requestBody: {required: true, content: {application/json: {schema: {type: object, required: [identifier,password], properties: {identifier: {type: string}, password: {type: string}}}}}}
      responses: {'200': {description: Authenticated}, '401': {description: Invalid credentials}}
  /v1/auth/refresh:
    post:
      tags: [Auth]
      responses: {'200': {description: Token rotated}, '401': {description: Invalid refresh token}}
  /v1/auth/verify-email:
    post: {tags: [Auth], responses: {'200': {description: Email verified}, '401': {description: Invalid or expired token}}}
  /v1/auth/resend-verification:
    post: {tags: [Auth], responses: {'200': {description: Verification message queued}}}
  /v1/auth/forgot-password:
    post: {tags: [Auth], responses: {'200': {description: Generic recovery response}}}
  /v1/auth/reset-password:
    post: {tags: [Auth], responses: {'200': {description: Password reset and sessions revoked}}}
  /v1/auth/me:
    get: {tags: [Auth], security: [{bearerAuth: []}], responses: {'200': {description: Current identity}}}
  /v1/auth/sessions:
    get: {tags: [Auth], security: [{bearerAuth: []}], responses: {'200': {description: Active sessions}}}
  /v1/auth/sessions/{session}:
    delete:
      tags: [Auth]
      security: [{bearerAuth: []}]
      parameters: [{name: session, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Session revoked}, '404': {description: Session not found}}
  /v1/auth/logout:
    post: {tags: [Auth], security: [{bearerAuth: []}], responses: {'200': {description: Current session revoked}}}
  /v1/auth/change-password:
    post:
      tags: [Auth]
      security: [{bearerAuth: []}]
      responses: {'200': {description: Password changed and other sessions revoked}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/merchant/payments:
    get:
      tags: [Merchant Payments]
      security: [{merchantBasic: []}]
      responses: {'200': {description: Merchant payment list}}
    post:
      tags: [Merchant Payments]
      security: [{merchantBasic: []}]
      parameters: [{name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 190}}]
      requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreatePaymentRequest'}}}}
      responses: {'201': {description: Payment created}, '409': {description: Idempotency or route conflict}}
  /v1/merchant/payments/{payment}/refunds:
    post:
      tags: [Refunds]
      security: [{merchantBasic: []}]
      parameters:
        - {name: payment, in: path, required: true, schema: {type: string}}
        - {name: Idempotency-Key, in: header, required: true, schema: {type: string}}
      responses: {'201': {description: Refund requested}, '409': {description: Payment is not refundable}}
  /v1/merchant/payments/{payment}:
    get:
      tags: [Merchant Payments]
      security: [{merchantBasic: []}]
      parameters: [{name: payment, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Merchant-owned payment detail}, '404': {description: Payment not found}}
  /v1/merchant/payment-links:
    get: {tags: [Payment Links], security: [{merchantBasic: []}], responses: {'200': {description: Payment links}}}
    post: {tags: [Payment Links], security: [{merchantBasic: []}], responses: {'201': {description: Payment link created}}}
  /v1/merchant/payment-links/{link}:
    get:
      tags: [Payment Links]
      security: [{merchantBasic: []}]
      parameters: [{name: link, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Merchant-owned payment link}, '404': {description: Payment link not found}}
  /v1/merchant/payment-links/{link}/activate:
    post:
      tags: [Payment Links]
      security: [{merchantBasic: []}]
      parameters: [{name: link, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Payment link activated}, '409': {description: Link cannot be activated}}
  /v1/merchant/payment-links/{link}/deactivate:
    post:
      tags: [Payment Links]
      security: [{merchantBasic: []}]
      parameters: [{name: link, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Payment link deactivated}}
  /v1/public/payment-links/{link}:
    get:
      tags: [Payment Links]
      parameters: [{name: link, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Active payment link}, '409': {description: Link unavailable}}
  /v1/public/payment-links/{link}/checkout:
    post:
      tags: [Payment Links]
      parameters: [{name: link, in: path, required: true, schema: {type: string}}]
      responses: {'201': {description: Checkout created atomically}}
  /v1/merchant/otp/send:
    post:
      tags: [OTP]
      security: [{merchantBasic: []}]
      parameters: [{name: Idempotency-Key, in: header, required: true, schema: {type: string}}]
      responses: {'201': {description: OTP queued}, '409': {description: Rate limited or provider unavailable}}
  /v1/merchant/otp/verify:
    post: {tags: [OTP], security: [{merchantBasic: []}], responses: {'200': {description: OTP verified}, '409': {description: Invalid or expired OTP}}}
  /v1/merchant/checkout-sessions:
    post:
      tags: [Checkout]
      security: [{merchantBasic: []}]
      responses: {'201': {description: Checkout created}}
  /v1/public/checkout/{checkout_id}:
    get:
      tags: [Checkout]
      parameters: [{name: checkout_id, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Public checkout}, '404': {description: Not found}}
  /v1/public/checkout/{checkout_id}/select-payment:
    post:
      tags: [Checkout]
      parameters: [{name: checkout_id, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Payment selected}, '409': {description: Checkout conflict}}
  /v1/public/checkout/{checkout_id}/status:
    get:
      tags: [Checkout]
      parameters: [{name: checkout_id, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Current public checkout status}, '404': {description: Not found}}
  /v1/provider/webhooks/{provider}:
    post:
      tags: [Provider Webhooks]
      parameters: [{name: provider, in: path, required: true, schema: {type: string, enum: [xendit,midtrans,tripay,ipaymu]}}]
      responses: {'200': {description: Accepted or duplicate}, '401': {description: Invalid signature}, '409': {description: Amount/state mismatch}}
  /v1/public/cms/pages/{slug}:
    get:
      tags: [CMS]
      parameters: [{name: slug, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Published CMS page}, '404': {description: Not found}}
  /v1/admin/dashboard:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Operational dashboard}, '403': {description: Permission denied}}}
  /v1/admin/payment-gateway-accounts:
    get: {tags: [Admin Payment Configuration], security: [{bearerAuth: []}], responses: {'200': {description: Provider accounts without secrets}}}
    post: {tags: [Admin Payment Configuration], security: [{bearerAuth: []}], responses: {'201': {description: Encrypted draft provider account}}}
  /v1/admin/payment-reconciliation:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Reconciliation records}}}
  /v1/admin/provider-health:
    get: {tags: [Admin Payment Configuration], security: [{bearerAuth: []}], responses: {'200': {description: Provider health and circuit metrics}}}
  /v1/admin/payment-gateway-accounts/{account}/test:
    post:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      parameters: [{name: account, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Provider connection tested}, '403': {description: Permission denied}}
  /v1/admin/wallets/{wallet}/adjustments:
    post:
      tags: [Admin]
      security: [{bearerAuth: []}]
      parameters: [{name: wallet, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Idempotent immutable wallet adjustment}, '409': {description: Insufficient balance or idempotency conflict}}
  /v1/app/workspaces:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      responses: {'200': {description: Workspaces owned by the current user}, '401': {description: Authentication required}}
  /v1/app/dashboard:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      responses: {'200': {description: Personal workspace dashboard}}
  /v1/app/profile:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      responses: {'200': {description: Current profile}}
    patch:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                full_name: {type: [string, 'null'], maxLength: 190}
                avatar_url: {type: [string, 'null'], format: uri, maxLength: 500}
                birth_date: {type: [string, 'null'], format: date}
      responses: {'200': {description: Profile updated}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/app/catalog:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      responses: {'200': {description: Active payment, PPOB, and SMM catalog}}
  /v1/app/activity:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {name: workspace, in: query, schema: {type: string, default: personal}}
        - {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
        - {name: per_page, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
      responses: {'200': {description: Paginated workspace activity}}
  /v1/app/notifications:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 100}}]
      responses: {'200': {description: Current user notifications}}
  /v1/app/notifications/{notification}/read:
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{name: notification, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Notification marked as read}, '404': {description: Notification not found}}
  /v1/app/merchants:
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      requestBody:
        required: true
        content: {application/json: {schema: {type: object, required: [business_name], properties: {business_name: {type: string, minLength: 3, maxLength: 190}}}}}
      responses: {'201': {description: Merchant workspace created}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/app/merchants/{merchant}/dashboard:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Owned merchant dashboard}, '403': {description: Merchant access denied}}
  /v1/app/merchants/{merchant}/settings:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Owned merchant settings without secrets}}
    put:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Merchant settings updated}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/app/merchants/{merchant}/api-keys:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: API keys without secret values}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'201': {description: API key created; secret is returned once}, '409': {description: Merchant not eligible for a live key}}
  /v1/app/merchants/{merchant}/api-keys/{key}:
    delete:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: key, in: path, required: true, schema: {type: string}}
      responses: {'200': {description: API key revoked}, '404': {description: API key not found}}
  /v1/app/merchants/{merchant}/bank-accounts:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Settlement accounts with masked account numbers}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bank_code, account_number, account_name]
              properties:
                bank_code: {type: string, minLength: 2, maxLength: 32}
                account_number: {type: string, pattern: '^[0-9]{6,34}$'}
                account_name: {type: string, minLength: 1, maxLength: 190}
      responses: {'201': {description: Encrypted bank account submitted for verification}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/app/merchants/{merchant}/domains:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Checkout domains owned by the merchant}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      requestBody: {required: true, content: {application/json: {schema: {type: object, required: [domain], properties: {domain: {type: string, format: hostname}}}}}}
      responses: {'201': {description: Domain verification TXT record created}, '409': {description: Domain already assigned}}
  /v1/app/merchants/{merchant}/domains/{domain}/verify:
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: domain, in: path, required: true, schema: {type: string, format: hostname}}
      responses: {'200': {description: Domain verified}, '409': {description: Required TXT record not found}}
  /v1/app/merchants/{merchant}/webhooks:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Merchant webhook endpoints without signing secrets}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: {type: string, format: uri, maxLength: 1000}
                events: {type: array, minItems: 1, items: {type: string, enum: [payment.paid, payment.failed, payment.expired, refund.updated, settlement.updated]}}
      responses: {'201': {description: Webhook created; signing secret is returned once}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/app/merchants/{merchant}/webhooks/{webhook}:
    delete:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: webhook, in: path, required: true, schema: {type: string}}
      responses: {'200': {description: Webhook revoked}, '404': {description: Webhook not found}}
  /v1/app/merchants/{merchant}/settlements:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Merchant settlement history}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 190}}
      requestBody:
        required: true
        content: {application/json: {schema: {type: object, required: [bank_account_id], properties: {bank_account_id: {type: string}, currency: {type: string, pattern: '^[A-Z]{3}$', default: IDR}}}}}
      responses: {'201': {description: Available funds atomically locked for settlement}, '409': {description: No settleable funds or verified destination}}
  /v1/app/merchants/{merchant}/ip-whitelist:
    get:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{name: merchant, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Merchant CIDR whitelist}}
    post:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters: [{name: merchant, in: path, required: true, schema: {type: string}}]
      responses: {'201': {description: CIDR whitelist entry created}}
  /v1/app/merchants/{merchant}/ip-whitelist/{entry}:
    delete:
      tags: [Merchant App]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: entry, in: path, required: true, schema: {type: string}}
      responses: {'200': {description: CIDR whitelist entry removed}, '404': {description: Entry not found}}
  /v1/app/merchants/{merchant}/payments:
    get: {tags: [Merchant App Payments], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: Owned merchant payments}}}
    post:
      tags: [Merchant App Payments]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 190}}
      requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreatePaymentRequest'}}}}
      responses: {'201': {description: Payment created}, '409': {description: Provider, routing, or idempotency conflict}}
  /v1/app/merchants/{merchant}/payments/{payment}:
    get:
      tags: [Merchant App Payments]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: payment, in: path, required: true, schema: {type: string}}
      responses: {'200': {description: Owned payment detail}, '404': {description: Payment not found}}
  /v1/app/merchants/{merchant}/payments/{payment}/refunds:
    post:
      tags: [Merchant App Payments]
      security: [{bearerAuth: []}]
      parameters:
        - {$ref: '#/components/parameters/MerchantId'}
        - {name: payment, in: path, required: true, schema: {type: string}}
        - {name: Idempotency-Key, in: header, required: true, schema: {type: string}}
      responses: {'201': {description: Refund requested}, '409': {description: Payment is not refundable}}
  /v1/app/merchants/{merchant}/checkout-sessions:
    post: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'201': {description: Hosted checkout session created}}}
  /v1/app/merchants/{merchant}/payment-links:
    get: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: Owned payment links}}}
    post: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'201': {description: Payment link created}}}
  /v1/app/merchants/{merchant}/payment-links/{link}:
    get: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: link, in: path, required: true, schema: {type: string}}], responses: {'200': {description: Owned payment link}}}
  /v1/app/merchants/{merchant}/payment-links/{link}/activate:
    post: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: link, in: path, required: true, schema: {type: string}}], responses: {'200': {description: Payment link activated}}}
  /v1/app/merchants/{merchant}/payment-links/{link}/deactivate:
    post: {tags: [Merchant App Checkout], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: link, in: path, required: true, schema: {type: string}}], responses: {'200': {description: Payment link deactivated}}}
  /v1/app/merchants/{merchant}/otp/send:
    post: {tags: [Merchant App OTP], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: Idempotency-Key, in: header, required: true, schema: {type: string}}], responses: {'201': {description: OTP queued}, '409': {description: Rate limited or provider unavailable}}}
  /v1/app/merchants/{merchant}/otp/verify:
    post: {tags: [Merchant App OTP], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: OTP verified}, '409': {description: Invalid or expired OTP}}}
  /v1/app/merchants/{merchant}/ppob/products:
    get: {tags: [Merchant App PPOB], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: Active PPOB products}}}
  /v1/app/merchants/{merchant}/ppob/orders:
    post: {tags: [Merchant App PPOB], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: Idempotency-Key, in: header, required: true, schema: {type: string}}], responses: {'201': {description: PPOB order accepted}, '409': {description: Provider unavailable or order conflict}}}
  /v1/app/merchants/{merchant}/ppob/orders/{order}:
    get: {tags: [Merchant App PPOB], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: order, in: path, required: true, schema: {type: string}}], responses: {'200': {description: Owned PPOB order}}}
  /v1/app/merchants/{merchant}/smm/services:
    get: {tags: [Merchant App SMM], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: Active SMM services}}}
  /v1/app/merchants/{merchant}/smm/orders:
    post: {tags: [Merchant App SMM], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: Idempotency-Key, in: header, required: true, schema: {type: string}}], responses: {'201': {description: SMM order accepted}, '409': {description: Provider unavailable or order conflict}}}
  /v1/app/merchants/{merchant}/smm/orders/{order}:
    get: {tags: [Merchant App SMM], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}, {name: order, in: path, required: true, schema: {type: string}}], responses: {'200': {description: Owned SMM order}}}
  /v1/app/merchants/{merchant}/reports:
    get: {tags: [Merchant App Reports], security: [{bearerAuth: []}], parameters: [{$ref: '#/components/parameters/MerchantId'}], responses: {'200': {description: Owned merchant aggregate report}}}
  /v1/merchant/ppob/products:
    get: {tags: [PPOB], security: [{merchantBasic: []}], responses: {'200': {description: Active PPOB products}}}
  /v1/merchant/ppob/orders:
    post:
      tags: [PPOB]
      security: [{merchantBasic: []}]
      parameters: [{name: Idempotency-Key, in: header, required: true, schema: {type: string}}]
      responses: {'201': {description: PPOB order accepted}, '409': {description: Provider unavailable or order conflict}}
  /v1/merchant/ppob/orders/{order}:
    get:
      tags: [PPOB]
      security: [{merchantBasic: []}]
      parameters: [{name: order, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: PPOB order detail}, '404': {description: Order not found}}
  /v1/merchant/smm/services:
    get: {tags: [SMM], security: [{merchantBasic: []}], responses: {'200': {description: Active SMM services}}}
  /v1/merchant/smm/orders:
    post:
      tags: [SMM]
      security: [{merchantBasic: []}]
      parameters: [{name: Idempotency-Key, in: header, required: true, schema: {type: string}}]
      responses: {'201': {description: SMM order accepted}, '409': {description: Provider unavailable or order conflict}}
  /v1/merchant/smm/orders/{order}:
    get:
      tags: [SMM]
      security: [{merchantBasic: []}]
      parameters: [{name: order, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: SMM order detail}, '404': {description: Order not found}}
  /v1/merchant/reports:
    get: {tags: [Reports], security: [{merchantBasic: []}], responses: {'200': {description: Merchant aggregate report}}}
  /v1/admin/users:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Paginated user list}, '403': {description: Permission denied}}}
  /v1/admin/merchants:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Paginated merchant list}, '403': {description: Permission denied}}}
  /v1/admin/merchants/{merchant}/status:
    patch:
      tags: [Admin]
      security: [{bearerAuth: []}]
      parameters: [{$ref: '#/components/parameters/MerchantId'}]
      responses: {'200': {description: Merchant status updated}, '403': {description: Permission denied}}
  /v1/admin/merchant-bank-accounts/{bank}/status:
    patch:
      tags: [Admin]
      security: [{bearerAuth: []}]
      parameters: [{name: bank, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Bank account verification status updated}, '403': {description: Permission denied}}
  /v1/admin/payments:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Paginated global payment list}, '403': {description: Permission denied}}}
  /v1/admin/settlements:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Paginated settlement list}, '403': {description: Permission denied}}}
  /v1/admin/settlements/{settlement}/complete:
    post:
      tags: [Admin]
      security: [{bearerAuth: []}]
      parameters: [{name: settlement, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Settlement completed atomically}, '409': {description: Invalid settlement state}}
  /v1/admin/settlements/{settlement}/fail:
    post:
      tags: [Admin]
      security: [{bearerAuth: []}]
      parameters: [{name: settlement, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Settlement failed and funds released atomically}, '409': {description: Invalid settlement state}}
  /v1/admin/audit-logs:
    get: {tags: [Admin], security: [{bearerAuth: []}], responses: {'200': {description: Paginated immutable audit log}, '403': {description: Permission denied}}}
  /v1/admin/media:
    get: {tags: [CMS], security: [{bearerAuth: []}], responses: {'200': {description: Media library}}}
    post:
      tags: [CMS]
      security: [{bearerAuth: []}]
      requestBody: {required: true, content: {multipart/form-data: {schema: {type: object, required: [file], properties: {file: {type: string, format: binary}}}}}}
      responses: {'201': {description: Validated media uploaded}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/admin/cms/pages:
    post:
      tags: [CMS]
      security: [{bearerAuth: []}]
      responses: {'201': {description: Draft CMS page created}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/admin/cms/pages/{page}:
    put:
      tags: [CMS]
      security: [{bearerAuth: []}]
      parameters: [{name: page, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: CMS draft revision updated}, '404': {description: Page not found}}
  /v1/admin/cms/pages/{page}/preview:
    get:
      tags: [CMS]
      security: [{bearerAuth: []}]
      parameters: [{name: page, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: CMS draft preview}, '404': {description: Page not found}}
  /v1/admin/cms/pages/{page}/publish:
    post:
      tags: [CMS]
      security: [{bearerAuth: []}]
      parameters: [{name: page, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: CMS revision published}, '409': {description: No publishable revision}}
  /v1/admin/cms/pages/{page}/rollback:
    post:
      tags: [CMS]
      security: [{bearerAuth: []}]
      parameters: [{name: page, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Published CMS revision rolled back}, '409': {description: No rollback revision}}
  /v1/admin/settings:
    get: {tags: [Admin Settings], security: [{bearerAuth: []}], responses: {'200': {description: Settings with secrets masked}, '403': {description: Permission denied}}}
  /v1/admin/settings/{group}/{key}:
    put:
      tags: [Admin Settings]
      security: [{bearerAuth: []}]
      parameters:
        - {name: group, in: path, required: true, schema: {type: string}}
        - {name: key, in: path, required: true, schema: {type: string}}
      responses: {'200': {description: Typed setting stored with secret encryption}, '403': {description: Permission denied}}
  /v1/admin/payment-gateways:
    get: {tags: [Admin Payment Configuration], security: [{bearerAuth: []}], responses: {'200': {description: Supported payment gateways}}}
  /v1/admin/payment-gateway-accounts/{account}/status:
    patch:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      parameters: [{name: account, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Provider account status updated}, '409': {description: Account must pass connection test before activation}}
  /v1/admin/payment-channels:
    get: {tags: [Admin Payment Configuration], security: [{bearerAuth: []}], responses: {'200': {description: Global payment channel catalog}}}
  /v1/admin/payment-channels/{channel}/status:
    patch:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      parameters: [{name: channel, in: path, required: true, schema: {type: string}}]
      responses: {'200': {description: Global channel status updated}}
  /v1/admin/payment-gateway-channels:
    post:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      responses: {'201': {description: Provider channel mapping created}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/admin/payment-routing:
    post:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      responses: {'201': {description: Payment routing rule created}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/admin/payment-pricing:
    post:
      tags: [Admin Payment Configuration]
      security: [{bearerAuth: []}]
      responses: {'201': {description: Payment pricing rule created}, '422': {$ref: '#/components/responses/ValidationError'}}
  /v1/admin/reports:
    get: {tags: [Reports], security: [{bearerAuth: []}], responses: {'200': {description: Admin aggregate report}}}
components:
  securitySchemes:
    bearerAuth: {type: http, scheme: bearer}
    merchantBasic: {type: http, scheme: basic}
  parameters:
    MerchantId: {name: merchant, in: path, required: true, schema: {type: string, pattern: '^mrc_'}}
  responses:
    ValidationError: {description: Validation failed}
  schemas:
    RegisterRequest:
      type: object
      required: [password]
      properties: {username: {type: string}, email: {type: string, format: email}, phone: {type: string}, password: {type: string, minLength: 12}}
    CreatePaymentRequest:
      type: object
      required: [reference,amount,currency,channel,customer]
      properties:
        reference: {type: string}
        amount: {oneOf: [{type: integer}, {type: string, pattern: '^[0-9]+(\\.[0-9]{1,2})?$'}]}
        currency: {type: string, default: IDR}
        channel: {type: string}
        customer: {type: object, required: [name,email], properties: {name: {type: string}, email: {type: string, format: email}, phone: {type: string}}}
