openapi: 3.1.0
info:
  title: TransferGo Business API
  description: "The **TransferGo Business API** lets businesses integrate their applications with TransferGo.\n\n## Authentication\n\nEvery request is secured using **Mutual TLS (mTLS)**. This includes the call to get an access token.\nYou must establish the connection with the X.509 client certificate issued to you by TransferGo,\nproving ownership of the matching private key on every call. The access token you obtain\nis **bound to the same certificate** and can't be used over a different connection\n([RFC 8705](https://datatracker.ietf.org/doc/html/rfc8705)).\n\n### Getting started\n\n1. Open an mTLS connection using your issued client certificate.\n2. Call `POST /oauth/v2/oauth-token` with `grant_type=client_credentials` to obtain an\n    access token. The token is valid for **5 minutes**.\n3. Call the business endpoints with `Authorization: Bearer <access_token>` over an mTLS\n    connection using the same certificate.\n\n## Support\n\nFor onboarding or integration help, [contact TransferGo Business support](https://support.transfergo.com/hc/en-gb/articles/20565275968274-Support-for-Business-Customers).\nWhen reporting an API issue, include the `CorrelationId` of the affected request."
  contact:
    name: TransferGo
    url: https://www.transfergo.com
  version: 1.0.0
servers:
  - url: https://business-api.transfergo.com
paths:
  /oauth/v2/oauth-token:
    post:
      tags:
        - Authentication
      summary: Get access token
      description: "Call this with an mTLS certificate of a client to get an access token for that client.\nThe token is valid for 5 minutes.\nThe token is bound to the mTLS certificate and can only be used in connections established using the same certificate."
      operationId: accessToken
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/GetOAuthTokenRequest'
        required: true
      responses:
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetOAuthTokenResponse'
      security:
        - mtls: [ ]
      x-codeSamples:
        - lang: cURL
          label: curl
          source: "curl -X POST \"https://business-api.transfergo.com/oauth/v2/oauth-token\" \\\n    --cert client.crt --key client.key \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    -d \"client_id=<client_id>\" \\\n    -d \"grant_type=client_credentials\" \\\n    -d \"scope=read\""
        - lang: PowerShell
          label: PowerShell
          source: "$pem = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile(\"client.crt\", \"client.key\")\n$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pem.Export(\"Pkcs12\"))\nInvoke-RestMethod -Method Post -Uri \"https://business-api.transfergo.com/oauth/v2/oauth-token\" `\n    -Certificate $cert `\n    -ContentType \"application/x-www-form-urlencoded\" `\n    -Body @{ client_id = \"<client_id>\"; grant_type = \"client_credentials\"; scope = \"read\" } `\n    | ConvertTo-Json"
  /v1/ping:
    get:
      tags:
        - Testing
      summary: Ping
      description: Use this endpoint to verify you can successfully connect to the TransferGo Business API.
      operationId: ping
      parameters:
        - name: produceError
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientPingEndpoints.PingSuccess'
      security:
        - oauth2:
            - read
          mtls: [ ]
      x-codeSamples:
        - lang: cURL
          label: curl
          source: "curl \"https://business-api.transfergo.com/v1/ping\" \\\n    --cert client.crt --key client.key \\\n    -H \"Authorization: Bearer <access_token>\""
        - lang: PowerShell
          label: PowerShell
          source: "$pem = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile(\"client.crt\", \"client.key\")\n$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pem.Export(\"Pkcs12\"))\nInvoke-RestMethod -Uri \"https://business-api.transfergo.com/v1/ping\" `\n    -Certificate $cert `\n    -Headers @{ Authorization = \"Bearer <access_token>\" } `\n    | ConvertTo-Json"
  /v1/transactions:
    get:
      tags:
        - Transactions
      summary: List transactions
      description: "Returns the financial operations of your TransferGo Business Account, newest first.\n\n## Pagination\n\nResults are paginated: when more results are available the response carries a `nextCursor`. Pass it\nback as the `cursor` parameter to fetch the next page. A missing `nextCursor` is the only signal\nthat you've reached the last page.\n\nThe filters are bound to the cursor: every request that carries a `cursor` must repeat the exact\nsame filter parameters as the request that produced it. Changing, adding, or omitting a filter\nfails with `400 CURSOR_FILTERS_CHANGED`. To change the filters, re-issue the request without a\n`cursor` to start a new result set. A cursor that has been altered fails with `400 INVALID_CURSOR`.\n\n`pageSize` accepts values between 1 and 100 and defaults to 10 when omitted; anything outside that\nrange fails with `400 INVALID_PAGE_SIZE`.\n\n## Transaction shape\n\nEvery transaction has a `type` - how it affected your balance - and a `subtype`, the business\nreason behind it (a top-up, a currency exchange, a savings movement, and so on). The `sender` and\n`receiver` show where the money came from and where it went.\n\nTransfers and top-ups carry both a `sender` and a `receiver`. Every other subtype - currency\nexchanges, savings deposits and withdrawals, interest, and balance adjustments - only touches one of\nyour balances, so the transaction sets just its `sender` or just its `receiver` and leaves the other\n`null`.\n\nYour own balances appear as a `transferGoAccount` or `savingsAccount`, where only `currency` is set.\nAn outside party - a bank account, card, payment link, and so on - instead has a `name` and a\n`display` string intended for showing to users (card numbers are masked, e.g. `**** 1234`).\n\nThe table below shows the `sender` and `receiver` you can expect for each `type` and `subtype`:\n\n| `type` | `subtype` | `sender` | `receiver` |\n| --- | --- | --- | --- |\n| `transfer` | `transfer` | External payer | External recipient |\n| `accountOutgoing` | `transfer` | Your TransferGo account | External recipient |\n| `accountIncoming` | `transfer` | External payer | Your TransferGo account |\n| `accountIncoming` | `topup` | External payer | Your TransferGo account |\n| `accountOutgoing` | `exchange` | Your TransferGo account | - |\n| `accountIncoming` | `exchange` | - | Your TransferGo account |\n| `accountOutgoing` | `savingsDeposit` | Your TransferGo account | - |\n| `accountIncoming` | `savingsDeposit` | - | Your savings account |\n| `accountOutgoing` | `savingsWithdrawal` | Your savings account | - |\n| `accountIncoming` | `savingsWithdrawal` | - | Your TransferGo account |\n| `accountIncoming` | `interest` | - | Your savings account |\n| `accountOutgoing` | `balanceAdjustment` | Your savings account | - |\n| `accountIncoming` | `balanceAdjustment` | - | Your savings account |\n\n## Statuses\n\n`statusReason` refines the `status` of some in-flight and failed transactions; for every other\ntransaction it's omitted. Only these combinations occur (new reasons may be added without notice):\n\n| `status` | possible `statusReason` |\n| --- | --- |\n| `pending` | `awaitingPayIn`, `awaitingApproval` |\n| `processing` | `payInProcessing` or `null` |\n| `failed` | `payInDeclined` or `null` |\n| all other statuses | always `null` |"
      operationId: listTransactions
      parameters:
        - name: fromCreatedAt
          in: query
          description: 'Only return transactions created at or after this instant (inclusive). ISO 8601, second precision; fractional seconds are rejected.'
          schema:
            type: string
            description: 'Only return transactions created at or after this instant (inclusive). ISO 8601, second precision; fractional seconds are rejected.'
            format: date-time
        - name: toCreatedAt
          in: query
          description: 'Only return transactions created at or before this instant (inclusive). ISO 8601, second precision; fractional seconds are rejected.'
          schema:
            type: string
            description: 'Only return transactions created at or before this instant (inclusive). ISO 8601, second precision; fractional seconds are rejected.'
            format: date-time
        - name: statuses
          in: query
          description: Only return transactions in any of these statuses; omit to return every status.
          schema:
            type: array
            items:
              title: TransactionStatus
              enum:
                - pending
                - processing
                - onHold
                - completed
                - failed
                - expired
                - cancelled
                - refunded
              type: string
            description: Only return transactions in any of these statuses; omit to return every status.
        - name: cursor
          in: query
          description: "Opaque cursor copied from the previous response's `nextCursor`. Omit to start from the first page.\nWhen sending a cursor, repeat the exact same filter parameters as the request that produced it.\nA changed or omitted filter is rejected with `CURSOR_FILTERS_CHANGED`."
          schema:
            type: string
            description: "Opaque cursor copied from the previous response's `nextCursor`. Omit to start from the first page.\nWhen sending a cursor, repeat the exact same filter parameters as the request that produced it.\nA changed or omitted filter is rejected with `CURSOR_FILTERS_CHANGED`."
        - name: pageSize
          in: query
          description: 'Maximum number of items per page, between 1 and 100. Defaults to 10 when omitted.'
          schema:
            maximum: 100
            minimum: 1
            type: integer
            description: 'Maximum number of items per page, between 1 and 100. Defaults to 10 when omitted.'
            format: int32
            default: 10
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedResponseOfTransaction'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
      security:
        - oauth2:
            - read
          mtls: [ ]
components:
  schemas:
    Amount:
      type: object
      properties:
        value:
          type: string
          format: decimal
        currency:
          type: string
      additionalProperties: false
    ApiErrorResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
      additionalProperties: false
    ClientPingEndpoints.PingSuccess:
      type: object
      properties:
        message:
          type: string
      additionalProperties: false
    GetOAuthTokenRequest:
      required:
        - client_id
        - grant_type
        - scope
      type: object
      properties:
        client_id:
          type: string
        grant_type:
          type: string
        scope:
          type: string
      additionalProperties: false
    GetOAuthTokenResponse:
      required:
        - access_token
        - expires_in
        - scope
        - token_type
      type: object
      properties:
        access_token:
          type: string
        token_type:
          type: string
        expires_in:
          type: integer
          format: int32
        scope:
          type: string
      additionalProperties: false
    PaginatedResponseOfTransaction:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Transaction'
        nextCursor:
          type: string
          nullable: true
      additionalProperties: false
    Receiver:
      type: object
      properties:
        type:
          $ref: '#/components/schemas/ReceiverType'
        name:
          type: string
          nullable: true
        display:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
      additionalProperties: false
    ReceiverType:
      type: string
      description: 'Known values: bankAccount, card, mobileWallet, accountIdentifier, paymentLink, transferGoAccount, savingsAccount, other. New values may be added without notice; unknown values should be handled gracefully.'
      x-extensible-enum:
        - bankAccount
        - card
        - mobileWallet
        - accountIdentifier
        - paymentLink
        - transferGoAccount
        - savingsAccount
        - other
    Sender:
      type: object
      properties:
        type:
          $ref: '#/components/schemas/SenderType'
        name:
          type: string
          nullable: true
        display:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
      additionalProperties: false
    SenderType:
      type: string
      description: 'Known values: bankAccount, card, blik, bancomatPay, mobileWallet, applePay, googlePay, transferGoAccount, savingsAccount, other. New values may be added without notice; unknown values should be handled gracefully.'
      x-extensible-enum:
        - bankAccount
        - card
        - blik
        - bancomatPay
        - mobileWallet
        - applePay
        - googlePay
        - transferGoAccount
        - savingsAccount
        - other
    Transaction:
      type: object
      properties:
        id:
          type: string
        type:
          $ref: '#/components/schemas/TransactionType'
        subtype:
          $ref: '#/components/schemas/TransactionSubtype'
        status:
          $ref: '#/components/schemas/TransactionStatus'
        statusReason:
          $ref: '#/components/schemas/TransactionStatusReason'
        createdAt:
          type: string
          format: date-time
        sendingAmount:
          $ref: '#/components/schemas/Amount'
        receivingAmount:
          $ref: '#/components/schemas/Amount'
        fee:
          $ref: '#/components/schemas/Amount'
        exchangeRate:
          type: string
          format: decimal
        sender:
          $ref: '#/components/schemas/Sender'
        receiver:
          $ref: '#/components/schemas/Receiver'
        reference:
          type: string
          description: "Free-text payment reference entered when the transfer was created, e.g. an invoice number.\nIt travels with the payment and is visible to the receiver. It isn't intended for\nprogrammatic matching. Use `reconciliationReference` for that."
          nullable: true
        reconciliationReference:
          type: string
          description: "Your own identifier for the transaction, as supplied by you when creating it (currently only supported in Batch Payments).\nUse it to reconcile transactions against records in your own systems.\nThere is no uniqueness constraint."
          nullable: true
        batchId:
          type: string
          nullable: true
      additionalProperties: false
    TransactionStatus:
      type: string
      description: 'Known values: pending, processing, onHold, completed, failed, expired, cancelled, refunded. New values may be added without notice; unknown values should be handled gracefully.'
      x-extensible-enum:
        - pending
        - processing
        - onHold
        - completed
        - failed
        - expired
        - cancelled
        - refunded
    TransactionStatusReason:
      type: string
      description: 'Known values: awaitingPayIn, awaitingApproval, payInProcessing, payInDeclined. New values may be added without notice; unknown values should be handled gracefully.'
      x-extensible-enum:
        - awaitingPayIn
        - awaitingApproval
        - payInProcessing
        - payInDeclined
    TransactionSubtype:
      type: string
      description: 'Known values: transfer, topup, exchange, balanceAdjustment, savingsDeposit, savingsWithdrawal, interest. New values may be added without notice; unknown values should be handled gracefully.'
      x-extensible-enum:
        - transfer
        - topup
        - exchange
        - balanceAdjustment
        - savingsDeposit
        - savingsWithdrawal
        - interest
    TransactionType:
      enum:
        - transfer
        - accountIncoming
        - accountOutgoing
      type: string
  securitySchemes:
    oauth2:
      type: oauth2
      description: OAuth2 client-credentials obtained over a mutually-authenticated TLS channel. The access token is bound to the presented X.509 client certificate (RFC 8705 — OAuth 2.0 Mutual-TLS).
      flows:
        clientCredentials:
          tokenUrl: /oauth/v2/oauth-token
          scopes:
            read: Read access to the public API.
    mtls:
      type: mutualTLS
      description: Mutual TLS. Clients must establish the connection with their issued X.509 client certificate; the certificate identifies the caller on every request (RFC 8705).
