openapi: 3.1.0
info:
  title: VortexPay Public API
  version: "2026-05-20"
  description: |
    API pública B2B de VortexPay para Pay-In, Pay-Out, balance y webhooks en Latam.
    Autenticación: OAuth2 Client Credentials o API Keys + HMAC en mutaciones.
  contact:
    name: VortexPay API Support
    url: https://docs.vortexpay.com
  license:
    name: Proprietary

servers:
  - url: https://api.vortexpay.com/api/v1
    description: Production
  - url: https://api.sandbox.vortexpay.com/api/v1
    description: Sandbox

security:
  - BearerAuth: []
  - ApiKeyAuth: []

tags:
  - name: Merchant
  - name: Balance
  - name: PayIns
  - name: PayOuts
  - name: Webhooks

paths:
  /merchant:
    get:
      tags: [Merchant]
      summary: Retrieve merchant
      operationId: getMerchant
      security:
        - BearerAuth: [merchant:read]
        - ApiKeyAuth: [merchant:read]
      responses:
        "200":
          description: Merchant profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Merchant"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /balance:
    get:
      tags: [Balance]
      summary: Retrieve balance
      operationId: getBalance
      parameters:
        - name: currency
          in: query
          schema:
            type: string
            pattern: "^[A-Z]{3}$"
      security:
        - BearerAuth: [balance:read]
        - ApiKeyAuth: [balance:read]
      responses:
        "200":
          description: Balance by currency
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Balance"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /payins:
    post:
      tags: [PayIns]
      summary: Create PayIn
      operationId: createPayIn
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [payins:write]
        - ApiKeyAuth: [payins:write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayInCreateRequest"
            examples:
              transfer_ar:
                summary: Transferencia Argentina
                value:
                  amount: { value: "15000.00", currency: "ARS" }
                  method: transfer
                  country: AR
                  description: Orden #ORD-8842
      responses:
        "201":
          description: PayIn created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayIn"
        "202":
          description: Accepted, processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayIn"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
    get:
      tags: [PayIns]
      summary: List PayIns
      operationId: listPayIns
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - $ref: "#/components/parameters/EndingBefore"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/PayInStatus"
        - name: currency
          in: query
          schema:
            type: string
      security:
        - BearerAuth: [payins:read]
        - ApiKeyAuth: [payins:read]
      responses:
        "200":
          description: List of PayIns
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayInList"

  /payins/{id}:
    get:
      tags: [PayIns]
      summary: Retrieve PayIn
      operationId: getPayIn
      parameters:
        - $ref: "#/components/parameters/PayInId"
      security:
        - BearerAuth: [payins:read]
        - ApiKeyAuth: [payins:read]
      responses:
        "200":
          description: PayIn
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayIn"
        "404":
          $ref: "#/components/responses/NotFound"

  /payins/{id}/cancel:
    post:
      tags: [PayIns]
      summary: Cancel PayIn
      operationId: cancelPayIn
      parameters:
        - $ref: "#/components/parameters/PayInId"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [payins:write]
        - ApiKeyAuth: [payins:write]
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CancelRequest"
      responses:
        "200":
          description: Canceled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayIn"
        "409":
          $ref: "#/components/responses/Conflict"

  /payouts:
    post:
      tags: [PayOuts]
      summary: Create PayOut
      operationId: createPayOut
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [payouts:write]
        - ApiKeyAuth: [payouts:write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayOutCreateRequest"
      responses:
        "202":
          description: Accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayOut"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
    get:
      tags: [PayOuts]
      summary: List PayOuts
      operationId: listPayOuts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/PayOutStatus"
      security:
        - BearerAuth: [payouts:read]
        - ApiKeyAuth: [payouts:read]
      responses:
        "200":
          description: List of PayOuts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayOutList"

  /payouts/{id}:
    get:
      tags: [PayOuts]
      summary: Retrieve PayOut
      operationId: getPayOut
      parameters:
        - $ref: "#/components/parameters/PayOutId"
      security:
        - BearerAuth: [payouts:read]
        - ApiKeyAuth: [payouts:read]
      responses:
        "200":
          description: PayOut
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayOut"

  /payouts/{id}/cancel:
    post:
      tags: [PayOuts]
      summary: Cancel PayOut
      operationId: cancelPayOut
      parameters:
        - $ref: "#/components/parameters/PayOutId"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [payouts:write]
        - ApiKeyAuth: [payouts:write]
      responses:
        "200":
          description: Canceled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayOut"

  /webhooks:
    post:
      tags: [Webhooks]
      summary: Create webhook
      operationId: createWebhook
      parameters:
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [webhooks:write]
        - ApiKeyAuth: [webhooks:write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookCreateRequest"
      responses:
        "201":
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
    get:
      tags: [Webhooks]
      summary: List webhooks
      operationId: listWebhooks
      security:
        - BearerAuth: [webhooks:read]
        - ApiKeyAuth: [webhooks:read]
      responses:
        "200":
          description: Webhook list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"

  /webhooks/{id}:
    delete:
      tags: [Webhooks]
      summary: Delete webhook
      operationId: deleteWebhook
      parameters:
        - $ref: "#/components/parameters/WebhookId"
        - $ref: "#/components/parameters/VortexTimestamp"
        - $ref: "#/components/parameters/VortexNonce"
        - $ref: "#/components/parameters/VortexSignature"
      security:
        - BearerAuth: [webhooks:write]
        - ApiKeyAuth: [webhooks:write]
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeleted"
        "204":
          description: No content

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth2 access token or API Key as Bearer
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: "Bearer vp_live_sk_... or vp_test_sk_..."

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        maxLength: 128
    VortexTimestamp:
      name: X-Vortex-Timestamp
      in: header
      required: true
      schema:
        type: string
    VortexNonce:
      name: X-Vortex-Nonce
      in: header
      required: true
      schema:
        type: string
        format: uuid
    VortexSignature:
      name: X-Vortex-Signature
      in: header
      required: true
      schema:
        type: string
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    StartingAfter:
      name: starting_after
      in: query
      schema:
        type: string
    EndingBefore:
      name: ending_before
      in: query
      schema:
        type: string
    PayInId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: "^pi_"
    PayOutId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: "^po_"
    WebhookId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: "^wh_"

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Authentication required
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Insufficient scope
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resource not found
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Conflict (idempotency or state)
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    UnprocessableEntity:
      description: Business rule violation
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    Money:
      type: object
      required: [value, currency]
      properties:
        value:
          type: string
          pattern: "^\\d+(\\.\\d{1,2})?$"
        currency:
          type: string
          pattern: "^[A-Z]{3}$"

    ErrorResponse:
      type: object
      required: [type, title, status, code, request_id]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
        request_id:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"

    FieldError:
      type: object
      properties:
        field:
          type: string
        code:
          type: string
        message:
          type: string

    Merchant:
      type: object
      required: [object, id, livemode, status, country, created_at]
      properties:
        object:
          type: string
          const: merchant
        id:
          type: string
        livemode:
          type: boolean
        business_name:
          type: string
        display_name:
          type: string
        email:
          type: string
          format: email
        country:
          type: string
        default_currency:
          type: string
        allowed_countries:
          type: array
          items:
            type: string
        allowed_currencies:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [active, suspended, pending_review]
        plan:
          type: string
          enum: [starter, growth, enterprise]
        created_at:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties:
            type: string

    Balance:
      type: object
      required: [object, livemode, available, updated_at]
      properties:
        object:
          type: string
          const: balance
        livemode:
          type: boolean
        available:
          type: array
          items:
            $ref: "#/components/schemas/Money"
        pending:
          type: array
          items:
            $ref: "#/components/schemas/Money"
        reserved:
          type: array
          items:
            $ref: "#/components/schemas/Money"
        updated_at:
          type: string
          format: date-time

    PayInStatus:
      type: string
      enum: [pending, processing, requires_action, succeeded, failed, canceled]

    PayOutStatus:
      type: string
      enum: [pending, processing, succeeded, failed, canceled]

    Party:
      type: object
      properties:
        type:
          type: string
          enum: [individual, business]
        name:
          type: string
        email:
          type: string
        document:
          type: object
          properties:
            type:
              type: string
            number:
              type: string
            country:
              type: string
        account:
          type: object
          properties:
            type:
              type: string
            number:
              type: string
            bank_code:
              type: string
            country:
              type: string

    PayInCreateRequest:
      type: object
      required: [amount, method, country]
      properties:
        amount:
          $ref: "#/components/schemas/Money"
        method:
          type: string
          enum: [transfer, qr, cvu, pix, spei, transfer_3_0]
        country:
          type: string
        description:
          type: string
          maxLength: 500
        payer:
          $ref: "#/components/schemas/Party"
        metadata:
          type: object
          additionalProperties:
            type: string
        return_url:
          type: string
          format: uri

    PayIn:
      type: object
      required: [object, id, livemode, status, amount, method, country, created_at, updated_at]
      properties:
        object:
          type: string
          const: payin
        id:
          type: string
        livemode:
          type: boolean
        status:
          $ref: "#/components/schemas/PayInStatus"
        amount:
          $ref: "#/components/schemas/Money"
        amount_received:
          $ref: "#/components/schemas/Money"
        method:
          type: string
        country:
          type: string
        description:
          type: string
        payer:
          $ref: "#/components/schemas/Party"
        action:
          type: object
          nullable: true
        failure_code:
          type: string
          nullable: true
        failure_message:
          type: string
          nullable: true
        metadata:
          type: object
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PayInList:
      type: object
      properties:
        object:
          type: string
          const: list
        data:
          type: array
          items:
            $ref: "#/components/schemas/PayIn"
        has_more:
          type: boolean
        url:
          type: string

    PayOutCreateRequest:
      type: object
      required: [amount, country, method, beneficiary]
      properties:
        amount:
          $ref: "#/components/schemas/Money"
        country:
          type: string
        method:
          type: string
          enum: [transfer, pix, spei, instant]
        description:
          type: string
        beneficiary:
          $ref: "#/components/schemas/Party"
        metadata:
          type: object

    PayOut:
      type: object
      required: [object, id, livemode, status, amount, country, method, created_at, updated_at]
      properties:
        object:
          type: string
          const: payout
        id:
          type: string
        livemode:
          type: boolean
        status:
          $ref: "#/components/schemas/PayOutStatus"
        amount:
          $ref: "#/components/schemas/Money"
        country:
          type: string
        method:
          type: string
        beneficiary:
          $ref: "#/components/schemas/Party"
        rail_reference:
          type: string
        failure_code:
          type: string
        metadata:
          type: object
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PayOutList:
      type: object
      properties:
        object:
          type: string
          const: list
        data:
          type: array
          items:
            $ref: "#/components/schemas/PayOut"
        has_more:
          type: boolean

    CancelRequest:
      type: object
      properties:
        reason:
          type: string
          enum: [customer_requested, duplicate, fraudulent, other]

    WebhookCreateRequest:
      type: object
      required: [url, events]
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        description:
          type: string

    Webhook:
      type: object
      properties:
        object:
          type: string
          const: webhook
        id:
          type: string
        livemode:
          type: boolean
        url:
          type: string
        events:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [enabled, disabled]
        secret:
          type: string
          description: Shown only on create
        created_at:
          type: string
          format: date-time

    WebhookList:
      type: object
      properties:
        object:
          type: string
          const: list
        data:
          type: array
          items:
            $ref: "#/components/schemas/Webhook"

    WebhookDeleted:
      type: object
      properties:
        object:
          type: string
          const: webhook
        id:
          type: string
        deleted:
          type: boolean
