openapi: 3.0.3

info:
  title: Bank Transfer (TED) Plugin API
  description: Complete API for Brazilian bank transfers (TED OUT, TED IN, P2P) through the Lerian platform, including transfer initiation, processing, status tracking, cancellation, and returns.
  version: 3.4.0
servers:
  - url: https://api.example.com
    description: Replace the example host with the URL provided during environment onboarding.
tags:
  - name: Transfers API
    description: Endpoints for initiating, processing, tracking, cancelling, and returning transfers.
  - name: Webhooks
    description: Endpoints for registering and managing tenant-scoped webhook subscriptions for transfer events.
  - name: Webhook DLQ
    description: Endpoints for inspecting and retrying webhook dead-letter queue messages.

security:
  - BearerAuth: []
  - OAuth2ClientCredentials:
      - api
  - OAuth2Password:
      - api

paths:
  /v1/transfers/initiate:
    post:
      tags:
        - Transfers API
      summary: Initiate a Transfer
      description: |-
        Use this endpoint to initiate a transfer by validating sender data, checking operating hours and usage limits, detecting duplicates, and calculating the fee. No funds are held at this step. The returned initiationId is valid for 24 hours and must be confirmed via the Process Transfer endpoint. Use the `X-Idempotency` header for guaranteed deduplication.
      operationId: initiateTransfer
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InitiateTransferRequest'
            examples:
              tedOut:
                summary: TED OUT to external bank
                value:
                  senderAccountId: 019c96a0-0c0c-7221-8cf3-13313fb60081
                  recipient:
                    ispb: "00000000"
                    branch: "0001"
                    account: "1234567890"
                    accountType: CACC
                    holderName: João Silva
                    holderDocument: "12345678901"
                  amount: 1000.50
                  description: Payment for services
                  metadata:
                    invoiceId: INV-2024-001
              p2p:
                summary: P2P (same institution)
                value:
                  senderAccountId: 019c96a0-0c0c-7221-8cf3-13313fb60081
                  recipient:
                    ispb: "12345678"
                    branch: "0001"
                    account: "9876543210"
                    accountType: CACC
                    holderName: Maria Santos
                    holderDocument: "98765432100"
                  amount: 500.00
                  description: Transfer to friend
      responses:
        '200':
          description: |-
            Indicates that the transfer was initiated successfully and is awaiting confirmation.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InitiateTransferResponse'
              examples:
                success:
                  summary: Initiation with fee
                  value:
                    initiationId: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b
                    feeAmount: 1.50
                    totalAmount: 1002.00
                    estimatedCompletionAt: '2026-02-01T18:00:00-03:00'
                    expiresAt: '2026-02-02T15:30:00-03:00'
                    status: PENDING_CONFIRMATION
                freeTransfer:
                  summary: No fee configured
                  value:
                    initiationId: 019c96a0-aa20-7bcd-e1f2-9d0e1f2a3b4c
                    feeAmount: 0.00
                    totalAmount: 1000.50
                    estimatedCompletionAt: '2026-02-01T18:00:00-03:00'
                    expiresAt: '2026-02-02T15:30:00-03:00'
                    status: PENDING_CONFIRMATION
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/AccountNotFound'
        '409':
          description: |-
            BTF-0012: an identical transfer was detected inside the deduplication window.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                duplicate:
                  summary: Same transfer submitted twice
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0012
                    title: Conflict
                    status: 409
                    detail: duplicate transfer detected
                    code: BTF-0012
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '422':
          description: |-
            BTF-0010: the transfer was attempted outside BACEN operating hours.

            BTF-0002: the request failed domain validation.

            BTF-0013: a database constraint refused the write.

            BTF-0004: this tenant cannot make this internal transfer. `reason` says why: `cross_organization` (the two accounts are in different Midaz organizations), `cross_ledger_disabled` (transfers between two ledgers of one organization are not enabled for this tenant) or `cross_ledger_flag_unreadable` (that setting could not be read; a later retry can succeed). Nothing was priced, held or written.

            BTF-0005: a transfer between two ledgers is not available. `reason` is `fee_mode_legacy` (the fee mode is not `native`) or `ledger_platform_below_minimum` (the Midaz ledger is older than 4.2.0).

            BTF-0006: a ledger of the transfer does not accept postings between ledgers. `reason` is `ledger_not_enabled`, and `ledgerId` names the ledger.

            BTF-0007: the two accounts hold different assets. `reason` is `asset_mismatch`.

            BTF-3003: the fee package of the source ledger takes the fee out of the amount. A transfer between two ledgers needs a source package that charges the fee on top. `reason` is `source_fee_deductible`. Nothing was reserved or written.

            BTF-4003: the recipient is on the ISPB of this institution, which this tenant shares with another engine, and the ownership map of the JD Courier says that no engine holds the account. Nothing was priced, held or written.

            BTF-4004: the recipient account in the request cannot be keyed for the ownership map: a blank account, a blank branch on an account that is not a payment account, or a value that the JD Courier refuses to normalize. Correct the request. A retry does not help.

            BTF-3002: the fee package that matches this TED OUT, or the package on the cross-core out route for a cross-core TED, takes the fee out of the amount. A TED OUT charges its fee on top of the amount, so the package must be non-deductible. Nothing was held or written.

            BTF-2005: the fee engine of the Midaz ledger refused to preview the fee, for example a deductible fee larger than the amount or two fee packages that tie. Nothing was held or written.

            BTF-0008: the `feeContext` is not accepted. No fee package on the payer's ledger selects on it, enabled or not, or the ledger does not price fees natively. Nothing was priced, held or written.

            BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

            BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

            BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                outsideHours:
                  summary: 'After 17:00'
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0010
                    title: Unprocessable Entity
                    status: 422
                    detail: 'transfers can only be initiated Monday-Friday between 06:30 and 17:00 America/Sao_Paulo time'
                    code: BTF-0010
                    category: deterministic
                    closeTime: '17:00'
                    currentTime: '2026-02-02T18:30:00-03:00'
                    nextAvailableTime: '2026-02-03T06:30:00-03:00'
                    openTime: '06:30'
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
                    timezone: America/Sao_Paulo
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: |-
            BTF-9000: an unexpected internal error occurred.

            BTF-9012: the origin ISPB configuration is missing.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '502':
          description: |-
            BTF-0501, BTF-3001: the CRM or the fee service returned a response that this service could not read.

            BTF-4001: the ownership map of the JD Courier answered outside its contract.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            A dependency is unavailable, for example the CRM (BTF-0502), the Midaz ledger (BTF-2000) or the fee service (BTF-3000).

            BTF-2000 also answers when the Midaz ledger did not answer the checks of a transfer between two ledgers. Nothing was priced, reserved or written. Retry later.

            BTF-4000: the ownership map of the JD Courier could not answer, or this tenant has no Courier configured. Retry later.

            BTF-4002: the ownership lookup was refused before it was sent.

            BTF-9015: the accounting routes for the ledger of this transfer could not be resolved. The document carries the `systemplaneKey` and `reason` members.

            BTF-9016: this tenant's database cannot record the initiation. Nothing was written and nothing was sent. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9018: this tenant binds more than one ISPB, so its clearing-house identity cannot be decided. Nothing was written, no hold was placed and nothing reached JD. Once the binding names exactly one ISPB, send the identical request under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                crmUnavailable:
                  summary: CRM service down
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0502
                    title: Service Unavailable
                    status: 503
                    detail: crm service unavailable
                    code: BTF-0502
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: crm
                feeServiceUnavailable:
                  summary: Fee service unavailable
                  value:
                    type: https://errors.lerian.studio/v1/BTF-3000
                    title: Service Unavailable
                    status: 503
                    detail: fee service unavailable
                    code: BTF-3000
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: fees

      x-mint:
        href: /pt/reference/interfaces/ted-jd/initiate-transfer
  /v1/transfers/process:
    post:
      tags:
        - Transfers API
      summary: Process a Transfer
      description: |-
        Use this endpoint to confirm and execute a previously initiated transfer. The system validates the initiationId, checks the sender's balance, holds funds in Midaz, and routes the transfer to JD SPB (for TED OUT), creates an internal Midaz transaction (for P2P), or sends the credit to the other core of your institution through the JD Courier (for a cross-core TED). A cross-core TED answers `COMPLETED` once the other core credits it, and `PROCESSING` while the outcome is unknown. Submitting the same initiationId returns the same transferId.
      operationId: processTransfer
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProcessTransferRequest'
            examples:
              process:
                summary: Process initiated transfer
                value:
                  initiationId: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b
      responses:
        '200':
          description: |-
            Indicates that the transfer was confirmed and processing has started.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProcessTransferResponse'
              examples:
                success:
                  summary: Transfer processing
                  value:
                    transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                    confirmationNumber: '20260201001'
                    status: PROCESSING
                    feeAmount: 1.50
                    totalAmount: 1002.00
        '202':
          description: |-
            Indicates that the TED OUT advance failed but the transfer was escalated to the reconciliation worker. The Midaz hold is intact. Poll `GET /v1/transfers/{transferId}` for the terminal status. The body is a problem document, not a transfer response.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                escalated:
                  summary: Escalated to reconciliation
                  value:
                    type: https://errors.lerian.studio/v1/BTF-9010
                    title: Accepted
                    status: 202
                    detail: ted_out advance failed; transfer escalated to reconciliation — poll transfer status
                    code: BTF-9010
                    category: transient
                    hint: 'GET /v1/transfers/{transferId} will converge to the final status'
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0201: the initiation was not found or belongs to a different organization.

            BTF-1092: the signing artifact was not found.

            BTF-2001: the account was not found in the Midaz ledger.

            BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                initiationNotFound:
                  summary: Invalid initiation ID
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0201
                    title: Not Found
                    status: 404
                    detail: payment initiation not found
                    code: BTF-0201
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '409':
          description: |-
            BTF-0012: an identical transfer was detected inside the deduplication window.

            BTF-0203: this initiation has already been processed into a transfer. The `transferId` member names that transfer.

            BTF-0112: the initiation was created before ledger-scoped routing and cannot be processed. Nothing was written. Initiate the transfer again.

            BTF-1094: the signing artifact was already used.

            BTF-1095: the signing artifact does not match the transfer.

            BTF-2002: the Midaz ledger reported a transaction state conflict. On a P2P between two ledgers, the ledger group is still in flight after the retries. Nothing is reverted. Send the confirmation again.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                alreadyProcessed:
                  summary: Initiation used
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0203
                    title: Conflict
                    status: 409
                    detail: this initiation has already been processed
                    code: BTF-0203
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
                    transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                legacyInitiation:
                  summary: Initiation created before ledger-scoped routing
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0112
                    title: Conflict
                    status: 409
                    detail: this payment initiation predates ledger-scoped accounting routes and cannot be processed; initiate the transfer again
                    code: BTF-0112
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '410':
          description: |-
            BTF-0202: the initiation expired after 24 hours. Create a new initiation.

            BTF-1093: the signing artifact expired.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                expired:
                  summary: Initiation too old
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0202
                    title: Gone
                    status: 410
                    detail: initiation expired after 24 hours. please create a new initiation.
                    code: BTF-0202
                    category: deterministic
                    expiresAt: '2026-01-31T15:30:00-03:00'
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '422':
          description: |-
            BTF-0002: the request failed domain validation.

            BTF-0004: the accounts of an internal transfer are now in different Midaz organizations (`reason` is `cross_organization`), or in ledgers other than the ones that the initiation recorded (no `reason`; initiate a new transfer). Nothing was posted.

            BTF-0005, BTF-0006, BTF-0007, BTF-3003: the P2P between two ledgers is refused, before the posting or by the Midaz ledger on the group posting. Nothing moved in either ledger. BTF-0005: the Midaz ledger is older than 4.2.0 (`reason` is `ledger_platform_below_minimum`). BTF-0006: a ledger is not enabled for postings between ledgers, or its bridge operation route is missing or invalid (`reason` is `ledger_not_enabled`, `bridge_route_missing` or `bridge_route_invalid`). BTF-0007: the two accounts hold different assets (`reason` is `asset_mismatch`). BTF-3003: the fee package of the source ledger became deductible after the initiation (`reason` is `source_fee_deductible`).

            BTF-0010: the transfer was attempted outside BACEN operating hours.

            BTF-0011: the transfer amount exceeds a configured usage limit.

            BTF-0013: a database constraint refused the write.

            BTF-1001: JD SPB rejected the transfer. The JD business-rejection code (DV*) is on `upstream.code`.

            BTF-1090: a TED OUT requires a signing artifact before it can be processed.

            BTF-1096: the digital signature failed validation.

            BTF-2003: the Midaz ledger rejected the transaction request.

            BTF-2004: the Tracer of the Midaz ledger denied the transaction under a configured limit or rule. Nothing was posted.

            BTF-2005: the fee engine of the Midaz ledger refused to price the transaction, for example a deductible fee larger than the amount. Nothing was posted.

            BTF-3002: this TED OUT or cross-core TED initiation was priced with a deductible fee package. A TED OUT must not carry one, because its fee is charged on top of the amount. Nothing reached JD, and this request leaves no hold. Make the package non-deductible, then initiate again. When the document carries `transferId`, an earlier process call of this initiation reserved that transfer, and any ledger hold it placed stays until an operator releases it.

            BTF-4005: the other core of your institution refused the credit of a cross-core TED, or the JD Courier reached no recipient. The hold was released, nothing moved, and the transfer is `REJECTED`.

            BTF-4003, BTF-4004: on a cross-core TED, the owner of the recipient account changed after the initiation, or an account cannot be keyed for the ownership map. No hold was placed, and the transfer is `FAILED`.

            BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

            BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

            BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                limitExceeded:
                  summary: Daily limit reached
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0011
                    title: Unprocessable Entity
                    status: 422
                    detail: usage limit exceeded
                    code: BTF-0011
                    attempted: '3000.00'
                    category: deterministic
                    current: '8000.00'
                    limit: '10000.00'
                    period: DAILY
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: |-
            BTF-9000: an unexpected internal error occurred.

            BTF-9002: the transfer was sent, and the bookkeeping after the dispatch failed. Do not retry.

            BTF-9011: the TED OUT stopped before it was sent to the clearing house, and the transfer keeps a live Midaz hold. The document carries `transferId`. Reconciliation picks the transfer up within ten minutes and releases its hold once the clearing house is open again. Do not retry `/process`. No operator action is required.

            BTF-9014: the P2P posting completed in the Midaz ledger, and the transfer was not recorded as completed. The document carries `transferId`. Read the transfer, then send the identical request again under a new `X-Idempotency` key. The repeat completes that same transfer and cannot move money twice. Do not initiate a new transfer for it.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '502':
          description: |-
            BTF-1007, BTF-1008, BTF-1009: JD SPB rejected the credentials, the signing certificate, or the configuration. The JD code is on `upstream.code`.

            BTF-4001: on a cross-core TED, the ownership map of the JD Courier answered outside its contract. No hold was placed.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                jdAuthRejected:
                  summary: JD SPB auth rejection
                  value:
                    type: https://errors.lerian.studio/v1/BTF-1008
                    title: Bad Gateway
                    status: 502
                    detail: 'jd spb rejected the request''s signing certificate or digital signature'
                    code: BTF-1008
                    upstream:
                      code: AAC90
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: jd_spb
        '503':
          description: |-
            A dependency is unavailable, for example JD SPB (BTF-1000) or Midaz (BTF-2000).

            On a P2P between two ledgers, BTF-2000 means that the outcome of the ledger group is unknown. Nothing is reverted. Send the confirmation again.

            BTF-4000, BTF-4002: on a cross-core TED, the ownership map of the JD Courier could not answer, the lookup was refused before it was sent, or the map does not show the sender's account as held by this engine. No hold was placed. Send the confirmation again later.

            BTF-9015: the accounting routes for the ledger of this transfer could not be resolved. The document carries the `systemplaneKey` and `reason` members.

            BTF-9016: this tenant's database cannot record the transfer. No hold was placed and nothing reached JD. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9018: this tenant binds more than one ISPB, so its clearing-house identity cannot be decided. Nothing was written, no hold was placed and nothing reached JD. Once the binding names exactly one ISPB, send the identical request under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                jdUnavailable:
                  summary: JD SPB unavailable
                  value:
                    type: https://errors.lerian.studio/v1/BTF-1000
                    title: Service Unavailable
                    status: 503
                    detail: jd spb service unavailable
                    code: BTF-1000
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: jd_spb
                midazUnavailable:
                  summary: Midaz ledger unavailable
                  value:
                    type: https://errors.lerian.studio/v1/BTF-2000
                    title: Service Unavailable
                    status: 503
                    detail: midaz service unavailable
                    code: BTF-2000
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: midaz

      x-mint:
        href: /pt/reference/interfaces/ted-jd/process-transfer
  /v1/transfers/signing/prepare:
    post:
      tags:
        - Transfers API
      summary: Prepare Transfer Signing Payload
      description: |-
        Use this endpoint to freeze the canonical STR0008 payload for a previously initiated TED OUT transfer and obtain the exact bytes that must be signed before calling Process Transfer. The response includes a `signingArtifactId` and a reserved `transferId`. The reserved `transferId` is not queryable until Process Transfer succeeds.

        Use the `X-Idempotency` header for guaranteed deduplication. Repeated calls with the same key replay the cached response.
      operationId: prepareSigning
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PrepareSigningRequest'
            examples:
              prepare:
                summary: Prepare signing for an initiation
                value:
                  initiationId: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b
      responses:
        '200':
          description: |-
            Indicates that the canonical payload was generated and is ready to be signed.

          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PrepareSigningResponse'
              examples:
                success:
                  summary: Signable payload returned
                  value:
                    signingArtifactId: 019c96a0-bb10-7def-a1b2-3c4d5e6f7a8b
                    transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                    controlNumber: '202602010001'
                    algorithm: RSA_PKCS1_V15_SHA256
                    payload: <STR0008>...</STR0008>
                    payloadHash: 8f0c7f5c1c7b4d9f2f5c7d1a0d4c9a2b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f
                    expiresAt: '2026-02-02T15:30:00-03:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0201: the initiation was not found or belongs to a different organization.

            BTF-1092: the signing artifact was not found.

            BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                initiationNotFound:
                  summary: Invalid initiation ID
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0201
                    title: Not Found
                    status: 404
                    detail: payment initiation not found
                    code: BTF-0201
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '409':
          description: |-
            BTF-0203: this initiation has already been processed into a transfer.

            BTF-0112: the initiation was created before ledger-scoped routing and cannot be processed. No signing artifact was written. Initiate the transfer again.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '410':
          $ref: '#/components/responses/InitiationExpired'
        '422':
          description: |-
            BTF-0002: the request failed domain validation.

            BTF-0003: the STR0008 payload is missing a required field. BTF-0004: signing prepare supports only TED OUT transfers.

            BTF-0013: a database constraint refused the write.

            BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

            BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

            BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                payloadFieldInvalid:
                  summary: Required payload field missing
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0003
                    title: Unprocessable Entity
                    status: 422
                    detail: 'str0008: transfer purpose is missing — provide a valid transfer purpose code in the transfer request'
                    code: BTF-0003
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
                unsupportedType:
                  summary: Unsupported transfer type
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0004
                    title: Unprocessable Entity
                    status: 422
                    detail: signing prepare is supported only for ted out transfers
                    code: BTF-0004
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/IdempotentInternalServerError'
        '502':
          description: |-
            BTF-0501: the CRM returned a response that this service could not read.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-9005, BTF-9007, BTF-9008: a dependency or the repository is unavailable.

            BTF-9016: this tenant's database cannot record the signing artifact. Nothing was written and nothing was sent. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9018: this tenant binds more than one ISPB, so its clearing-house identity cannot be decided. Nothing was written, no hold was placed and nothing reached JD. Once the binding names exactly one ISPB, send the identical request under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/prepare-transfer-signing
  /v1/transfers/ted-in/poll:
    post:
      tags:
        - Transfers API
      summary: Trigger TED IN Poller
      description: |-
        Use this endpoint to force one immediate JD inbound poll cycle for the current tenant. This is an operational endpoint intended to accelerate receipt of JD return messages without waiting for the scheduled poll interval. It does not require the `X-Organization-Id` header — the tenant is derived from the authenticated context.

        Same-key retries replay the cached response instead of draining JD again. An in-flight duplicate is rejected with 409 by the route-level idempotency middleware.
      operationId: triggerTEDInPoller
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
      responses:
        '200':
          description: |-
            Indicates that the poll cycle completed. The `outcome` field reports the cycle result.

          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TEDInPollResponse'
              examples:
                success:
                  summary: Cycle drained 3 messages
                  value:
                    outcome: success
                    processed: 3
                empty:
                  summary: Nothing to process
                  value:
                    outcome: empty
                    processed: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Indicates that no poller tenant/configuration was found for the authenticated context.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0107: another TED IN poll or replay cycle is already in progress for this tenant.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                busy:
                  summary: Cycle already in progress
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0107
                    title: Conflict
                    status: 409
                    detail: ted in poll cycle already in progress
                    code: BTF-0107
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/IdempotentInternalServerError'
        '503':
          description: |-
            BTF-9005: a dependency is unavailable.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '504':
          $ref: '#/components/responses/IdempotentGatewayTimeout'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/trigger-ted-in-poll
  /v1/transfers/ted-in/replay:
    post:
      tags:
        - Transfers API
      summary: Replay TED IN Backlog
      description: |-
        Use this endpoint to force one immediate replay pass over persisted, unprocessed JD inbound messages for the current tenant. Unlike `/ted-in/poll`, this route does not read JD's destructive queue — it only reprocesses durable backlog rows already persisted locally.

        Use the `X-Idempotency` header for guaranteed deduplication.
      operationId: replayTEDInPoller
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
      responses:
        '200':
          description: |-
            Indicates that the replay pass completed. The `outcome` field reports the cycle result and `replayed` is the number of backlog rows reprocessed.

          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TEDInReplayResponse'
              examples:
                success:
                  summary: Replayed 2 backlog rows
                  value:
                    outcome: success
                    replayed: 2
                    held: 0
                    claimed: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Indicates that no replay tenant/configuration was found for the authenticated context.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0107: another TED IN poll or replay cycle is already in progress for this tenant.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                busy:
                  summary: Cycle already in progress
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0107
                    title: Conflict
                    status: 409
                    detail: ted in poll cycle already in progress
                    code: BTF-0107
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/IdempotentInternalServerError'
        '503':
          description: |-
            BTF-9005: a dependency is unavailable.

            BTF-9000: the replay pass partially failed, or the idempotency store was unavailable before the handler ran. In the second case nothing ran: send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '504':
          $ref: '#/components/responses/IdempotentGatewayTimeout'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/replay-ted-in-poll
  /v1/transfers/ted-in/resume:
    post:
      tags:
        - Transfers API
      summary: Resume TED IN Poller
      description: |-
        Use this endpoint to clear the fail-closed at-most-once receive latch and re-arm a latched or dead TED IN poller without restarting the pod. This is a higher-privilege operator endpoint than `/ted-in/poll` because it re-opens JD destructive-read consumption. Reach for it only when auto-recovery cannot revive the poller — a panicked multi-tenant child or a single-tenant poller past the panic hard-cap.

        Resume never bypasses the money-path durable-gap gate. When the live receive-state-unknown latch is set, the current latch's reconciliation-gap row must be durable before the poller resumes; otherwise the request is refused with `409` and the poller stays dead. Verify the pending gap with [List TED IN Reconciliation Gaps](/pt/reference/interfaces/ted-jd/list-ted-in-reconciliation-gaps) before calling resume.

        The optional body `{acknowledge, note}` additionally marks the current latch's reconciliation gap resolved and stamps an operator annotation on it. `acknowledge` defaults to `false`, leaving the gap OPEN so it remains visible for review. A healthy, not-latched poller returns `200` with `resumed: false` and `reason: "not_latched"` — the call is a safe idempotent no-op. Same-key retries replay the cached response.

        The route is organization-independent and does not require the `X-Organization-Id` header — the tenant is resolved from the authenticated context. In multi-tenant deployments, an inactive tenant is rejected with `503`.
      operationId: resumeTEDInPoller
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
      requestBody:
        required: false
        description: Optional resume options. An empty body re-arms the poller and leaves the reconciliation gap open.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TEDInResumeRequest'
            examples:
              rearmOnly:
                summary: Re-arm the poller, leave the gap open
                value: {}
              acknowledgeGap:
                summary: Re-arm and mark the current gap resolved
                value:
                  acknowledge: true
                  note: verified against the JD cabine; no inbound message lost
      responses:
        '200':
          description: |-
            Indicates that the resume attempt completed. `resumed` is `true` when a latched or dead poller was re-armed and `false` for the healthy no-op. `gapResolved` is `true` only when `acknowledge` was requested AND the gap row was successfully marked resolved. `reason` is the state resumed from (`receive_state_unknown`, `panic`, `auth_failure`) or `not_latched` for the no-op.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TEDInResumeResponse'
              examples:
                resumed:
                  summary: Latched poller re-armed
                  value:
                    resumed: true
                    gapResolved: false
                    reason: receive_state_unknown
                    tenantId: 11111111-1111-1111-1111-111111111111
                notLatched:
                  summary: Healthy no-op
                  value:
                    resumed: false
                    gapResolved: false
                    reason: not_latched
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: |-
            BTF-0108: resume refused. The receive-state-unknown latch has no durable reconciliation gap yet, so the poller stays dead. Retry after the gap row is persisted.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/IdempotentInternalServerError'
        '503':
          description: |-
            BTF-9005: a dependency is unavailable.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '504':
          $ref: '#/components/responses/IdempotentGatewayTimeout'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/resume-ted-in-poller
  /v1/transfers/ted-in/reconciliation-gaps:
    get:
      tags:
        - Transfers API
      summary: List unresolved TED IN reconciliation gaps
      description: |-
        Use this endpoint to list durable traces of ambiguous TED IN inbound-message outcomes that an operator must verify. This is a read-only administrative endpoint; the resolve or close action is a separate concern. It is organization-independent and does not require the `X-Organization-Id` header. In multi-tenant deployments the results are scoped to the caller's resolved tenant database.
      operationId: listTEDInReconciliationGaps
      parameters:
        - name: limit
          in: query
          description: Maximum number of records to return.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          description: Number of records to skip for pagination.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Indicates that the request was successful and the matching reconciliation gaps are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReconciliationGapsList'
              examples:
                list:
                  summary: One unresolved gap
                  value:
                    items:
                      - id: 018f3a1c-1b2c-7def-8a9b-0c1d2e3f4a5b
                        organizationId: 770e8400-e29b-41d4-a716-446655440003
                        tenantId: 28b2302f-9c1d-4e5a-b6c7-8d9e0f1a2b3c
                        transportSubreason: timeout_unknown_outcome
                        lastSequenceCheckpoint: SEQ-42
                        latchedAt: '2026-07-21T13:35:00Z'
                        createdAt: '2026-07-21T13:35:00Z'
                    pagination:
                      limit: 50
                      offset: 0
                      returned: 1
                      totalCount: 1
                      hasNextPage: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-ted-in-reconciliation-gaps
  /v1/transfers/ted-in/poller:
    get:
      tags:
        - Transfers API
      summary: Get TED IN Poller Status
      description: |-
        Use this endpoint to check whether the inbound TED poller is enabled, running, and stale, with its stop reason and last cycle timestamps, without scraping the readiness probe. The status uses the same rules as the TED IN poller check of `/readyz`, so the two cannot disagree on what stale or down means. A disabled poller (JD polling turned off) is a configuration fact: it answers `200` with `enabled: false`, not an error.

        This is a read-only endpoint. It never provisions, starts, or resumes a poller. It does not require the `X-Organization-Id` header. In multi-tenant deployments the body describes the poller of the caller's resolved tenant, not the deployment-wide aggregate that the readiness probe publishes, and carries `tenantId`. In single-tenant deployments `tenantId` is omitted.
      operationId: getTEDInPollerStatus
      responses:
        '200':
          description: Indicates that the poller status was returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TEDInPollerStatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '503':
          description: 'BTF-9005: the TED IN poller is not configured in this deployment.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '504':
          description: 'BTF-9000: the status read exceeded the request deadline.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/get-ted-in-poller-status
  /v1/transfers/ted-in/retained:
    get:
      tags:
        - Transfers API
      summary: List Retained TED IN Messages
      description: |-
        Use this endpoint to list the inbound JD messages that this service neither credited nor returned, oldest first, so an operator can see what is stuck, why, for how long, and how much money is behind it.

        By default (`state=open`) the list shows only what is still open. A retention closed through [Close Unsettled TED IN Credit](/pt/reference/interfaces/ted-jd/close-unsettled-ted-in-credit) leaves this list and every retained gauge, which lets an alert on those numbers clear. `state=closed` or `state=all` bring the closed rows back, each with its outcome, its note, and the person who wrote it.

        Six retention reasons are written. A person ends four of them:

        - `UNRECOGNIZED_TYPE`: the message type is not one that this service consumes.
        - `ACCOUNT_NOT_OWNED`: the recipient account is not one that this service owns.
        - `CLASSIFICATION_FAILED`: the credit carries an amount that nothing can credit, or it is an STR0041R2 (INSS payroll loan) whose bank tariff is not zero.
        - `RECIPIENT_NOT_FOUND_RETURN_UNAUTHORIZED`: the recipient was not found, and the tenant has not authorized the automatic return to the clearing house.

        Two end by themselves and carry `drained: false`:

        - `LEDGER_ROUTE_UNRESOLVED`: no ledger routing binding answers the organization, ledger, and transfer type that the credit was refused for (`TED_OUT` for a chargeback compensation, which debits the beneficiary).
        - `RECIPIENT_AMBIGUOUS_ORGANIZATIONS`: the recipient sits in several of the tenant's own organizations under the receiving ISPB.

        These two are written only when the tenant's database carries the current migrations. Before that, a routing hold writes no row and appears on no list, so an empty list does not prove that no credit is held. The poller asks their question again on every cycle. When the configuration answers it, the credit lands and the row leaves this list with no operator action, so the close endpoint refuses them with BTF-0217. That check does not see a recipient re-homed in CRM: after such a repair, call [Replay TED IN Backlog](/pt/reference/interfaces/ted-jd/replay-ted-in-poll), which re-drives every held row once. A credit held because the ISPB-to-organization binding could not be read writes no row and is not listed here. It lands by itself once the read recovers.

        The `reason` filter also accepts `ACCOUNT_UNASSIGNED`, `AMBIGUOUS_CONTROL_ID`, and `OWNERSHIP_DECISION_UNAVAILABLE`. No row carries these reasons, so a filter on one of them returns an empty list.

        Returning funds is irreversible; retention is not. A `CLASSIFICATION_FAILED` STR0041R2 also carries `consignedCreditAmount` and `bankTariff` beside `amount`. Apart from those two amounts, nothing from the original message is rendered: `contentBytes` says how much is held without showing any of it. A closed row carries the operator's note verbatim. The note is free text that a member of staff wrote about their own decision, 10 to 500 characters, never derived from the message and not sanitized. The retained records kept on the undeliverable side are listed by [List Undeliverable TED IN Credits](/pt/reference/interfaces/ted-jd/list-undeliverable-ted-in-credits) with `status=RETAINED`.

        This is a read-only administrative endpoint. It does not require the `X-Organization-Id` header. In multi-tenant deployments the list is scoped to the caller's resolved tenant database.
      operationId: listRetainedTEDInMessages
      parameters:
        - name: reason
          in: query
          description: Filter by retention reason.
          schema:
            type: string
            enum:
              - UNRECOGNIZED_TYPE
              - ACCOUNT_NOT_OWNED
              - ACCOUNT_UNASSIGNED
              - RECIPIENT_NOT_FOUND_RETURN_UNAUTHORIZED
              - CLASSIFICATION_FAILED
              - AMBIGUOUS_CONTROL_ID
              - LEDGER_ROUTE_UNRESOLVED
              - RECIPIENT_AMBIGUOUS_ORGANIZATIONS
              - OWNERSHIP_DECISION_UNAVAILABLE
        - name: state
          in: query
          description: Closure window. `open` (default) is what is still stuck and what every retained gauge counts; `closed` is what an operator already settled, with who, when, and how; `all` is both.
          schema:
            type: string
            enum:
              - open
              - closed
              - all
            default: open
        - name: limit
          in: query
          description: Maximum number of records to return.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          description: Number of records to skip for pagination. Advance it by the `returned` value of the previous page.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Indicates that the request was successful and the matching retained messages are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetainedTEDInMessageList'
        '400':
          description: |-
            BTF-0001: the tenant context or a query parameter is invalid. BTF-0210: the retention reason is outside the declared vocabulary. BTF-0214: `state` is not `open`, `closed`, or `all`.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: |-
            BTF-9005: the retained-message reader is not configured in this deployment.

            BTF-9016: this tenant's database does not carry the retention-closure columns. Apply the pending migrations to this tenant, then send the request again.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-retained-ted-in-messages
  /v1/transfers/ted-in/undeliverables:
    get:
      tags:
        - Transfers API
      summary: List Undeliverable TED IN Credits
      description: |-
        Use this endpoint to list the inbound TED credits that could not be delivered, so an operator can tell a recipient that does not exist from a ledger refusal, and see how far the re-querying of each one has got, without database access. The rejection reason is a fixed sentence derived from the category; no customer identity or upstream error text is exposed.

        This is a read-only administrative endpoint. It does not require the `X-Organization-Id` header. In multi-tenant deployments the list is scoped to the caller's resolved tenant database.
      operationId: listUndeliverableTEDInCredits
      parameters:
        - name: status
          in: query
          description: Filter by resolution status.
          schema:
            type: string
            enum:
              - PENDING
              - IN_PROGRESS
              - RETAINED
              - RETURNED
              - ABSORBED
              - FAILED
        - name: category
          in: query
          description: Filter by rejection category.
          schema:
            type: string
            enum:
              - RECIPIENT_NOT_FOUND
              - RECIPIENT_ACCOUNT_INVALID
              - MALFORMED_MESSAGE
              - FEE_RESOLUTION_FAILED
              - MIDAZ_REJECTION
              - CREDITED_AMOUNT_ZERO
              - ORGANIZATION_MISSING
              - ISPB_NOT_OWNED_BY_TENANT
        - name: limit
          in: query
          description: Maximum number of records to return.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          description: Number of records to skip for pagination. Advance it by the `returned` value of the previous page.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Indicates that the request was successful and the matching undeliverable credits are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UndeliverableTEDInCreditList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: 'BTF-9005: the undeliverable-credit reader is not configured in this deployment.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-undeliverable-ted-in-credits
  /v1/transfers/ted-in/unsettled/{messageId}/close:
    post:
      tags:
        - Transfers API
      summary: Close Unsettled TED IN Credit
      description: |-
        Use this endpoint to record where an unsettled inbound credit actually went, after a person moved it by hand. An inbound credit is unsettled when its message carries an open retention (neither credited nor returned), or when its devolution sits in `RETAINED` (a return that the tenant never authorized) or `FAILED` (a return that the clearing house refused).

        No money moves here, and no ledger or JD call is made. Every outcome names an act performed outside this system, and this endpoint is the record of it:

        - `RETURNED`: a person sent the funds back to the paying bank, out of band, under the return control number that the devolution row has kept since the day of receipt.
        - `CREDITED`: a person credited the beneficiary.
        - `ABSORBED`: the tenant keeps funds that reached neither end.

        A credit written down twice, as an open retention and as a `RETAINED` devolution, is closed on both rows in one transaction or on neither. A retention that the poller resolves by itself is refused: the credit lands on its own once the blocking condition lifts, and declaring it settled would tell the tenant to keep money that the service is about to deliver.

        This is a tenant-wide administrative action. It requires the `transfers:resolve` permission and a user token, because the closure records which person decided. It does not require the `X-Organization-Id` header. In multi-tenant deployments it is scoped to the caller's resolved tenant database.
      operationId: closeUnsettledTEDInCredit
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: messageId
          in: path
          required: true
          description: The identifier of the inbound JD message.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        description: Where the money went, and why.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CloseUnsettledTEDInCreditRequest'
      responses:
        '200':
          description: Indicates that the closure was recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CloseUnsettledTEDInCreditResponse'
        '400':
          description: |-
            BTF-0001: invalid `messageId`, missing idempotency key, an `outcome` other than `RETURNED`, `CREDITED`, or `ABSORBED`, or a `note` outside 10 to 500 characters.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |-
            BTF-0405: the caller lacks the `transfers:resolve` permission. BTF-0406: the bearer token is not a person (a service account or a token of another type), so this by-hand write cannot be attributed to an operator. No grant fixes this; call the endpoint with a user token. A user token whose owner or subject is only whitespace is refused with 401 BTF-0401 before this check.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '404':
          description: |-
            BTF-0215: this message carries nothing that an operator can close: no retention, and no devolution in `RETAINED` or `FAILED`. A message that does not exist answers the same code, so the endpoint cannot reveal which message IDs a tenant holds.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0216: somebody already recorded where this money went. Read their decision before you write another.

            BTF-0217: this retention resolves by itself and must not be closed by hand. The credit lands on its own once its ledger route or ownership answer arrives, and the remedy is that configuration, never this endpoint.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: 'BTF-9000: internal server error. BTF-9006: unexpected empty response from the service layer.'
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-9005, BTF-9007, BTF-9008: a dependency or the repository is unavailable.

            BTF-9016: this tenant's database does not carry the retention-closure columns, and nothing was written. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/close-unsettled-ted-in-credit
  /v1/transfers/{transferId}:
    get:
      tags:
        - Transfers API
      summary: Get a Transfer
      description: |-
        Use this endpoint to retrieve the current status and details of a transfer, including its type, status history, sender and recipient details, amounts, and timestamps. Only returns transfers belonging to the same organization.
      operationId: getTransferStatus
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the transfer.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Indicates that the transfer was found and its details are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transfer'
              examples:
                tedOutCompleted:
                  summary: TED OUT completed
                  value:
                    transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                    type: TED_OUT
                    status: COMPLETED
                    organizationId: 019c96a0-0a98-7287-9a31-786e0809c769
                    ledgerId: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
                    senderAccountId: 019c96a0-0c0c-7221-8cf3-13313fb60081
                    recipient:
                      ispb: '00000000'
                      branch: '0001'
                      account: '1234567890'
                      accountType: CACC
                      holderName: Maria Santos
                      holderDocument: '98765432100'
                    amount: 1000.50
                    feeAmount: 1.50
                    totalAmount: 1002.00
                    confirmationNumber: '20260201001'
                    controlNumber: '202602010001'
                    createdAt: '2026-02-01T18:30:00Z'
                    updatedAt: '2026-02-01T18:35:00Z'
                    completedAt: '2026-02-01T18:35:00Z'
                    statusHistory:
                      - status: CREATED
                        timestamp: '2026-02-01T18:30:00Z'
                      - previousStatus: CREATED
                        status: PENDING
                        timestamp: '2026-02-01T18:30:05Z'
                      - previousStatus: PENDING
                        status: PROCESSING
                        timestamp: '2026-02-01T18:30:10Z'
                      - previousStatus: PROCESSING
                        status: COMPLETED
                        timestamp: '2026-02-01T18:35:00Z'
                    reconciliationAttempts: 0
                tedInReceived:
                  summary: TED IN received
                  value:
                    transferId: 019c96a0-ab20-7def-a1b2-1f2a3b4c5d6e
                    type: TED_IN
                    status: COMPLETED
                    organizationId: 019c96a0-0a98-7287-9a31-786e0809c769
                    ledgerId: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
                    senderAccountId: 019c96a0-7e5a-7b3c-9d8e-2f1a0b3c4d5e
                    recipient:
                      ispb: '12345678'
                      branch: '0001'
                      account: '9876543210'
                      accountType: CACC
                      holderName: João Silva
                      holderDocument: '12345678901'
                    amount: 500.00
                    feeAmount: 0.00
                    totalAmount: 500.00
                    confirmationNumber: '20260201002'
                    controlNumber: '202602010002'
                    createdAt: '2026-02-01T19:00:00Z'
                    updatedAt: '2026-02-01T19:00:05Z'
                    completedAt: '2026-02-01T19:00:05Z'
                    statusHistory:
                      - status: RECEIVED
                        timestamp: '2026-02-01T19:00:00Z'
                      - previousStatus: RECEIVED
                        status: COMPLETED
                        timestamp: '2026-02-01T19:00:05Z'
                    reconciliationAttempts: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/TransferNotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/retrieve-transfer
  /v1/transfers:
    get:
      tags:
        - Transfers API
      summary: List Transfers
      description: |-
        Use this endpoint to retrieve a paginated list of transfers. You can filter by ledger, type, status, reconciliation status, date range, and amount range. The default limit is 50 items, with a maximum of 200. Only returns transfers belonging to the same organization.
      operationId: listTransfers
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - name: ledgerId
          in: query
          description: Filter results by Midaz ledger, inside the `X-Organization-Id` scope. It matches the ledger that the transfer is filed under (the payer's ledger) or, on a P2P between two ledgers, the recipient's ledger. The filter applies to the page and to `totalCount`. A blank value applies no filter. A malformed UUID or the all-zeroes UUID returns `400 BTF-0001`.
          schema:
            type: string
            format: uuid
        - name: type
          in: query
          description: Filter results by transfer type. `TED_IN` does not select the customer credits that carry a `TED_IN_STR` type, nor the cross-core credits and returns received from the other core of your institution, which carry `CROSS_CORE_IN`. `CROSS_CORE` selects the cross-core TEDs sent.
          schema:
            $ref: '#/components/schemas/TransferType'
        - name: status
          in: query
          description: Filter results by transfer status.
          schema:
            $ref: '#/components/schemas/TransferStatus'
        - name: reconciliationStatus
          in: query
          description: Filter results by reconciliation status. `MANUAL_REVIEW` also selects the transfers stored as `EXHAUSTED`.
          schema:
            type: string
            enum: [AWAITING, RESOLVED, REVERSING, REVERTED, MANUAL_REVIEW]
        - name: createdFrom
          in: query
          description: The start of the date range filter in RFC3339 format.
          schema:
            type: string
            format: date-time
        - name: createdTo
          in: query
          description: The end of the date range filter in RFC3339 format.
          schema:
            type: string
            format: date-time
        - name: minAmount
          in: query
          description: The minimum transfer amount to include in the results. Must be 0 or greater.
          schema:
            type: number
            format: decimal
            minimum: 0
        - name: maxAmount
          in: query
          description: The maximum transfer amount to include in the results. This filter has no upper bound.
          schema:
            type: number
            format: decimal
            minimum: 0
        - name: limit
          in: query
          description: Maximum number of transfers to return.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          description: Number of transfers to skip.
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: sortDirection
          in: query
          description: Sort direction for results ordered by createdAt.
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        '200':
          description: Indicates that the request was successful and the matching transfers are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransferList'
              examples:
                list:
                  summary: Paginated transfers
                  value:
                    items:
                      - transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                        type: TED_OUT
                        status: COMPLETED
                        organizationId: 019c96a0-0a98-7287-9a31-786e0809c769
                        ledgerId: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
                        senderAccountId: 019c96a0-0c0c-7221-8cf3-13313fb60081
                        amount: 1000.50
                        feeAmount: 1.50
                        totalAmount: 1002.00
                        createdAt: '2026-02-01T18:30:00Z'
                        updatedAt: '2026-02-01T18:35:00Z'
                      - transferId: 019c96a0-ab20-7def-a1b2-1f2a3b4c5d6e
                        type: TED_IN
                        status: COMPLETED
                        organizationId: 019c96a0-0a98-7287-9a31-786e0809c769
                        ledgerId: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
                        senderAccountId: 019c96a0-7e5a-7b3c-9d8e-2f1a0b3c4d5e
                        amount: 500.00
                        feeAmount: 0.00
                        totalAmount: 500.00
                        createdAt: '2026-02-01T19:00:00Z'
                        updatedAt: '2026-02-01T19:05:00Z'
                    pagination:
                      limit: 50
                      offset: 0
                      returned: 2
                      totalCount: 2
                      hasNextPage: false
        '400':
          $ref: '#/components/responses/InvalidFilter'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-transfers
  /v1/transfers/{transferId}/cancel:
    post:
      tags:
        - Transfers API
      summary: Cancel a Transfer
      description: |-
        Use this endpoint to cancel a transfer that has not yet been submitted for processing. Only transfers in CREATED or PENDING status can be cancelled. A `TED_OUT` that returns a TED IN cannot be cancelled in any status. Cancelling a transfer releases the held funds in Midaz and sets the status to CANCELLED. Calling cancel on an already-cancelled transfer returns success.

        This endpoint takes no request body — the transfer is identified by the path parameter alone.
      operationId: cancelTransfer
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the transfer to cancel.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: |-
            Indicates that the transfer was successfully cancelled and funds were released.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelTransferResponse'
              examples:
                success:
                  summary: Cancellation successful
                  value:
                    transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                    status: CANCELLED
                    cancelledAt: '2026-02-01T18:35:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/TransferNotFound'
        '409':
          description: |-
            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          description: |-
            BTF-0204: the current status of the transfer does not allow cancellation, or the transfer is the return of a TED IN.

            BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

            BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

            BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                cannotCancel:
                  summary: Already processing
                  value:
                    type: https://errors.lerian.studio/v1/BTF-0204
                    title: Unprocessable Entity
                    status: 422
                    detail: transfer cannot be cancelled in its current status
                    code: BTF-0204
                    category: deterministic
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: |-
            BTF-9000: an unexpected internal error occurred.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-9005, BTF-9007, BTF-9008: a dependency or the repository is unavailable.

            BTF-9016: this tenant's database cannot record the cancellation, and the Midaz hold was left intact. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/cancel-transfer
  /v1/transfers/{transferId}/return:
    post:
      tags:
        - Transfers API
      summary: Return a Transfer
      description: |-
        Use this endpoint to return one completed transfer to the party that paid it. The return is a new transfer that names the original in `originalTransferId`. What happens depends on the type of the original.

        **TED IN.** The original must be a `COMPLETED` `TED_IN` received as STR0008R2. The plugin places a hold of the credited amount on the recipient's account. It then sends a voluntary STR0010, return reason code `70`, which returns the gross amount of the TED IN to the paying bank. The return is a `TED_OUT` with no fee, and the answer is `PROCESSING`. The final outcome arrives later. When the STR settles the return, the plugin reverts the original credit in the ledger. When nothing proves whether the STR0010 left, the answer carries `reconciliationStatus` `AWAITING`, and reconciliation decides. A customer credit with a `TED_IN_STR` type cannot be returned.

        A TED IN return runs only inside the operating window of the tenant. On 24 December and on the last business day of the year, the window for returns closes no later than 13:00. The return posts on the `TED_OUT` ledger routing of the ledger of the TED IN. It cannot be cancelled or sent back to the reconciliation queue by hand.

        **P2P.** The original must be a `COMPLETED` `P2P` that is not a return itself. The ledger reverses the original transaction in this request: the return debits the account that received the original and credits the account that paid it. The answer is `COMPLETED`, and the original stays `COMPLETED`. A P2P return has no operating window, no hold, and no ledger routing. On a P2P inside one ledger, the plugin first checks the balance of the account that received the original. If the outcome in the ledger is unknown, the answer is `503 BTF-2000`, and `transferId` names the return, still `CREATED`. Send the same request again under a new `X-Idempotency` key: it resumes that return and never creates a second one.

        A return needs Midaz 4.1.3 or later, and 4.2.0 or later for a P2P between two ledgers. A transfer has one live return at most. The body is optional and accepts only `externalReference`. A request with an application token must send it.

        This endpoint requires the `transfers:return` permission.
      operationId: returnTransfer
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the TED IN or P2P to return.
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        description: The reference of your system for this return. Required when the token belongs to an application.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReturnTransferRequest'
      responses:
        '201':
          description: |-
            Indicates that the return was created. The body depends on `type`: `TED_OUT` for the return of a TED IN, `P2P` for the return of a P2P.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ReturnIncomingTransferResponse'
                  - $ref: '#/components/schemas/ReturnInternalTransferResponse'
        '400':
          description: |-
            BTF-0001: `transferId` is not a UUID, the `X-Idempotency` key is missing or invalid, or the body is not one JSON document, carries a member other than `externalReference`, or carries an `externalReference` outside `^[A-Za-z0-9._:/-]{1,128}$`. A request with an application token that carries no `externalReference` gets the same answer. Nothing was created.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/TransferNotFound'
        '409':
          description: |-
            BTF-0119: the transfer already has a live return. `transferId` names that return, and `originalTransferId` names the transfer that you asked to return. A TED IN accepts a new return only after the earlier one ends `FAILED`, `CANCELLED`, or `REJECTED` with no clearing control number and no reversal recorded. A P2P accepts a new return after the earlier one ends `FAILED`. A P2P return that is still `CREATED` is not refused: the request resumes it.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          description: |-
            BTF-0118: the transfer cannot be returned. It is neither a `COMPLETED` TED IN received as STR0008R2 that carries the original clearing data, nor a `COMPLETED` P2P that is not a return itself. Nothing was created.

            BTF-0120: the ledger cannot reverse the original transaction. `reason` is `route_not_reversible`, `original_not_approved`, `fee_account_insufficient`, or `ledger_platform_below_minimum`, and nothing was reserved or sent. On a P2P return, `reason` can also be `ledger_refused`: the ledger refused the reversal with the code in `upstream.code`, and `transferId` names the return, now `FAILED`.

            BTF-0121: the ledger refused the hold on the account that received the original. `reason` is `insufficient_balance` or `prior_reservation_pending`, and `transferId` names the return, now `FAILED`. On a P2P inside one ledger, the plugin refuses `insufficient_balance` before it creates anything.

            BTF-0006: on a P2P return between two ledgers, the ledger refused to reverse the group. `reason` is `ledger_not_enabled`, `bridge_route_missing`, or `bridge_route_invalid`, with the ledger code in `upstream.code`. BTF-0007: on the same return, the assets do not match (`reason` is `asset_mismatch`). BTF-2004: on a P2P return, the Tracer of the ledger denied the reversal or sent it to review. In these three cases, `transferId` names the return, now `FAILED`, and nothing moved. Request the return again under a new `X-Idempotency` key once the cause is fixed.

            BTF-0010: the clearing house is closed for returns, or the holiday calendar could not be read (`classification` is `holiday_lookup_dependency_unavailable`). Nothing was created.

            BTF-1001: JD SPB refused the STR0010. `upstream.code` carries the JD code, and `transferId` names the return.

            BTF-1007: JD SPB reports that the password of the plugin expired (`upstream.code` is `AAE08`). `transferId` names the return.

            BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

            BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

            BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: |-
            BTF-9000: an unexpected internal error occurred. Read the original transfer before you send anything else. On a P2P return, `transferId` can name the return, still `CREATED`.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '502':
          description: |-
            BTF-1007: JD SPB rejected the credentials of the plugin. BTF-1008: JD SPB rejected the signing certificate or the digital signature. BTF-1009: JD SPB rejected the configuration. Each one carries the JD code in `upstream.code` and names the return in `transferId`.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-0124: the plugin cannot make sure now that the transfer has one live return at most. Nothing was created. When the cause clears, request the return again under a new `X-Idempotency` key.

            BTF-2000: the ledger did not answer. `transferId` names the return when it exists. A TED IN return is then closed by the plugin. A P2P return stays `CREATED`, and its outcome in the ledger is unknown: send the same request again under a new `X-Idempotency` key. It resumes that return and never creates a second one. When the answer carries `Retry-After`, wait that many seconds first.

            BTF-1000: JD SPB was unavailable, and nothing left the plugin. `transferId` names the return.

            BTF-9015: the ledger routing of this return could not be resolved. Nothing was created.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/return-transfer
  /v1/transfers/{transferId}/reconciliation/retry:
    post:
      tags:
        - Transfers API
      summary: Retry Transfer Reconciliation
      description: |-
        Use this endpoint to return an outbound TED parked in manual review to the reconciliation queue, so the worker queries its settlement again. No transfer is re-sent and no hold is touched: only the reconciliation state moves back to `AWAITING`, with a zeroed attempt counter. The return of a TED IN cannot be sent back to the queue. The operator `reason` is required and is recorded in the audit trail.
      operationId: retryTransferReconciliation
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the transfer.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        description: Operator justification for the requeue.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RetryTransferReconciliationRequest'
      responses:
        '200':
          description: Indicates that the transfer returned to the reconciliation queue.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetryTransferReconciliationResponse'
        '400':
          description: |-
            BTF-0001: invalid `transferId`, missing idempotency key, or an operator `reason` that is missing or longer than 500 characters.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0200: transfer not found. BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0109: the transfer is not a `TED_OUT` parked in `PROCESSING` and `MANUAL_REVIEW` (or its legacy equivalent `EXHAUSTED`) with a preserved hold, or it is the return of a TED IN. Nothing was written, and repeating the request cannot change that.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: 'BTF-9000: internal server error. BTF-9006: unexpected empty response from the service layer.'
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-9005, BTF-9007, BTF-9008: a dependency or the repository is unavailable.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/retry-transfer-reconciliation
  /v1/transfers/{transferId}/reconciliation/resolve:
    post:
      tags:
        - Transfers API
      summary: Resolve Transfer Reconciliation
      description: |-
        Use this endpoint to resolve by hand one transfer that reconciliation parked in manual review.

        On an outbound TED whose hold is preserved:

        - `SETTLE` commits the hold and completes the transfer.
        - `REVERT` cancels the hold and fails the transfer.

        On an inbound credit whose STR0010R2 chargeback refund the automatic path could not finish:

        - `RE_POST` pays the refund again. Before it pays anything, it asks the ledger what became of the compensation already on file. Only a compensation that moved nothing licenses a posting. One that already moved the gross closes the row where it stands, with nothing posted. One that moved any other amount is refused for good. Even the licensed case first searches the ledger for this chargeback's own refunds and adopts a live one already paying the gross instead of filing a second, so a refund that was paid can never be paid twice. A compensation on file that the ledger prices as live enters that same search as one of its members, so while a second live refund stands beside it, the row is not closed on it and no difference is quoted against it.
        - `SETTLED_BY_HAND` posts nothing and records that the refund was arranged outside this platform, so it requires an `evidenceReference` that names the outside movement. Because this record also takes the row off the pending list, it is filed only over a ledger reading that completed and priced every posting it saw. A scan that the ledger truncated, a scan that it could not answer, and a scan holding a posting in a status this service cannot price are each refused as not confirmed instead of closed. Send the same request again under a new `X-Idempotency` key.

        When the ledger holds more than one live refund for a chargeback, this platform debited the beneficiary more than once, and no action here takes a posting back: `RE_POST` is refused, and `SETTLED_BY_HAND` closes the row with every one of those transactions named on the closing record.

        On every action except `RE_POST`, the ledger moves first, and the row is written only once the ledger reaches the state that the resolution claims, so a failure midway leaves the transfer exactly as it was found. `RE_POST` runs in the other order, because it is the one action that pays: the new compensation is stamped on the transfer the moment the ledger returns its ID, before anything judges what it paid. Every later failure leaves the row parked and naming the money, and repeating the identical request under a new `X-Idempotency` key (the key that earned the failure is fenced) converges on that stamp instead of paying a second time. A stamped compensation that the ledger cannot find is undecided, not absent, and is refused: wait, repeat the identical request under a new `X-Idempotency` key, and close the row with `SETTLED_BY_HAND` if it never clears. A refund that the ledger prices at anything other than the gross is a verdict that no retry changes, whether it is the compensation on file, one that the ledger scan found, or the one that this request filed: the row stays parked, the remaining difference is settled outside this platform, and the row is closed with `SETTLED_BY_HAND`.

        On a voluntary return of a TED IN (a `TED_OUT` that names the TED IN in `originalTransferId`), the reason code on the row (`reconciliationReasonCode`) decides which actions resolve it:

        - `return_settlement_unknown` and `return_settlement_unknown_exhausted`: `SETTLE` or `REVERT`. `SETTLE` requires `clearingControlNumber`, the STR control number read from the clearing house.
        - `return_hold_release_pending`: `REVERT`.
        - `return_already_returned_at_str`: `SETTLE` or `REVERT`.
        - `return_settled_revert_pending`: `SETTLE`.
        - `return_revert_failed` and `return_settled_after_rejection`: `SETTLE` or `SETTLED_BY_HAND`.
        - `return_duplicate_settlement` and `return_returned_by_payer`: `SETTLED_BY_HAND`.

        A TED IN whose chargeback arrived after its voluntary return is parked under `chargeback_after_return` and closes with `SETTLED_BY_HAND`. Any other pair of reason code and action, and a pair that the state of the row does not allow, is refused with 409 BTF-0110. On a return that already carries a settlement proof, a `clearingControlNumber` that differs from that proof is also refused with 409 BTF-0110. Everywhere else the plugin ignores `clearingControlNumber` and does not store it.

        The operator `reason` is required on every action. It is recorded in the audit trail with the subject resolved from the bearer token. This endpoint requires the `transfers:resolve` permission and a user token.
      operationId: resolveTransferReconciliation
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the transfer.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        description: Operator decision, justification, and evidence reference (required for `SETTLED_BY_HAND`).
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResolveTransferReconciliationRequest'
      responses:
        '200':
          description: |-
            Indicates that the transfer was resolved. On a voluntary return of a TED IN, `reconciliationStatus` can also be `AWAITING`, when the row went back to the reconciliation worker, or `MANUAL_REVIEW`, when the row stays parked with the failure recorded on it. A ledger reference that names nothing is omitted instead of sent as the all-zeroes UUID: `compensationTransactionId` and `previousCompensationTransactionId` belong to the inbound actions, and `midazTransactionId` is absent on an inbound close of a row whose credit transaction was never proven.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolveTransferReconciliationResponse'
        '400':
          description: |-
            BTF-0001: invalid `transferId`, missing idempotency key, an `action` other than `SETTLE`, `REVERT`, `RE_POST`, or `SETTLED_BY_HAND`, a `reason` outside 10 to 500 characters, an `evidenceReference` longer than 200 characters, a `SETTLED_BY_HAND` that carries no `evidenceReference`, or a `clearingControlNumber` outside `^[A-Za-z0-9]{1,20}$`. On a voluntary return of a TED IN, it also answers a request that lacks a field that the reason code of the row requires. The body then carries `transferId`.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |-
            BTF-0405: the caller lacks the `transfers:resolve` permission. BTF-0406: the bearer token is not a person (a service account or a token of another type), so this by-hand write cannot be attributed to an operator. No grant fixes this; call the endpoint with a user token. A user token whose owner or subject is only whitespace is refused with 401 BTF-0401 before this check.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '404':
          description: |-
            BTF-0200: transfer not found. BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0110: the transfer is not a row that this action resolves, and nothing was resolved. On a voluntary return of a TED IN, the return does not accept this action under its reason code, and the body carries `transferId`. Otherwise, `SETTLE` and `REVERT` need an outbound TED parked in manual review with a preserved hold. `RE_POST` and `SETTLED_BY_HAND` need an inbound credit parked in manual review, and `RE_POST` also needs a compensation already on file. BTF-0110 also answers an inbound row whose compensation on file is no longer the one that the request was made against. The body then carries `compensationTransactionIdOnFile` and `expectedCompensationTransactionId` as bare transaction UUIDs. Either one is omitted when that column is empty, which is the ordinary state of a chargeback that the automatic path still owes a refund. Read the transfer again and resolve it against the compensation it now carries.

            BTF-0111: the ledger contradicts the requested resolution. The body carries `ledgerStatus`, omitted only when the ledger returned the transaction without one. Issue the other action. Only outbound transfers get this answer, because the other action on an inbound row closes the transfer.

            BTF-0115: the ledger holds a live posting for this chargeback on which this service cannot complete the chargeback: it moved an amount other than the gross, or its entries refund none of it. No retry changes either condition, because the amount and the entries of a transaction are the same on every later read. The posting can be the compensation already on file, a posting that the pre-post ledger scan found and that the transfer does not name, or the refund that this request just filed and stamped. The remedy is the whole of it: read that transaction in the ledger, settle whatever remains outside this platform, and close the transfer with `SETTLED_BY_HAND`. The body carries `transactionId`, the posting to open (which, when the ledger scan found it, is not the one that the transfer names), and `ledgerStatus`, omitted only when the ledger returned the transaction without one. The body carries no amount under any key, because no one measured a number beside a live posting.

            BTF-0116: a hand `RE_POST` filed its refund and the ledger cancelled it, so it paid nothing. This is not a shortfall: the cancelled refund is filed on the transfer and paid nothing, so there is nothing to settle outside this platform. Issue `RE_POST` again to file a new refund. The body carries `transactionId`, `paidAmount` (zero), `grossAmount`, and `ledgerStatus`.

            BTF-0117: the ledger holds more than one live refund for this one chargeback, so this platform debited the beneficiary more than once. Nothing was posted, and the transfer was not closed. Only `RE_POST` gets this answer, because `SETTLED_BY_HAND` records the same transactions on the closing record and closes the row. No action of this endpoint takes a refund back: read every transaction named here and return the surplus to the beneficiary outside this platform. Another `RE_POST` would file a third debit. Once the surplus is returned, `SETTLED_BY_HAND` records every transaction named here on the closing record and closes the transfer. The body carries `transactionIds`, every live refund for this chargeback that the ledger walk reached, as bare transaction UUIDs, and `liveRefundSetComplete`, which says whether those are all of them. The set is incomplete on any of four exits: a page that the ledger refused, a page answered with nothing, a refused direct read of a live candidate, and the page budget. On every exit except the refused direct read, the walk stops where it broke and does not resume, so a pair proven at that break is still this refusal while the ledger can hold a further live refund that nothing named. The detail says the same in words.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: |-
            BTF-9000: internal server error. BTF-9006: unexpected empty response from the service layer.

            BTF-9014: the ledger moved and the transfer could not be brought to rest: an outbound hold that finalized and left the row unwritten, a refund that a hand `RE_POST` filed, which the transfer already names and whose closing was refused, or a refund that may have been filed and that the transfer does not name. Read the transfer, then send the identical request again under a new `X-Idempotency` key to converge the row with the ledger. The same key is refused with 422 BTF-0014 (`refusalCode: IDEMPOTENCY_OUTCOME_UNRECORDED`) for the retention window when this answer carried `X-Idempotency-Fenced: true`, and with 409 BTF-0016 while the in-flight lease holds when it carried `false`. The repeat converges on the ledger state of this transfer, not on the key, so it cannot move money twice. If the repeat does not clear the error, the ledger and the transfer row still disagree, and the transfer needs manual inspection.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '502':
          description: |-
            BTF-2000: the ledger could not confirm the resolution. The transfer was not changed, and the identical request can be sent again under a new `X-Idempotency` key. The key that earned this 502 is refused with 422 BTF-0014 (`refusalCode: IDEMPOTENCY_OUTCOME_UNRECORDED`) for the retention window when this answer carried `X-Idempotency-Fenced: true`, and with 409 BTF-0016 while the in-flight lease holds when it carried `false`.

            On a `RE_POST`, nothing was posted and nothing was stamped. BTF-2000 also covers a compensation that the ledger has not decided yet: one that it still holds live, and one that it cannot find at all, which is read as undecided instead of absent, so that a stamp written against the wrong ledger can never license a second real debit. It also answers both inbound actions when the count of live refunds for this chargeback did not finish: a result set that the page walk could not reach the end of, a ledger that could not be asked, or a refund in a status that this service cannot price, which the detail names. Neither closing the row nor quoting a difference is done over a count that was never established. Repeat the identical request under a new key. A `SETTLED_BY_HAND` that never clears is a row to inspect, not one to close blind.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-1000: on a voluntary return of a TED IN, JD did not answer the query of the control number. Nothing changed. The body carries `transferId`.

            BTF-2000: on a voluntary return of a TED IN, the ledger did not answer, or the revert that the action ran did not confirm. The body carries `transferId` and a `hint`. The hint says either to send the request again under a new `X-Idempotency` key, or not to resend and to read the transfer, because the row already went back to the reconciliation worker. A resend of that second case is refused with 409 BTF-0110.

            BTF-9005: a required service dependency is not configured. BTF-9007: the transfer repository timed out. BTF-9008: the transfer repository is unavailable.

            On an inbound closing, BTF-9008 also covers a failure to enqueue the chargeback announcement that the closing owes the client. The announcement rides inside the closing transaction, so nothing was written, no money moved, and the same request can be repeated under a new `X-Idempotency` key. BTF-9008 also answers an inbound `RE_POST` or `SETTLED_BY_HAND` whose chargeback refund the ledger already held and whose row the storage would not point at it. No hand action moved anything, nothing was posted, the transfer is exactly where it was found, and the same request can be sent again under a new `X-Idempotency` key, which reads the ledger again, finds that refund, and stamps it.

            BTF-9016: this tenant's database cannot record the resolution's history row. Nothing was written and no ledger call was made. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/resolve-transfer-reconciliation
  /v1/transfers/{transferId}/scope/repair:
    post:
      tags:
        - Transfers API
      summary: Repair Transfer Ledger Scope
      description: |-
        Use this endpoint to correct the accounting scope of one transfer whose ledger is the all-zeroes UUID. That value addresses no Midaz ledger and matches no routing binding, so the transfer's refund stays held, and no configuration that an operator could write frees it.

        The operator supplies the ledger, because only a person may authorize an edit to the scope of a money row. CRM must report the same ledger for the transfer's own account before anything is written; if the two disagree, nothing is written. No money moves here and no ledger call is made: the next reconciliation or poller cycle moves the money, once, on the routes that the repaired ledger resolves.

        The operator `reason` is required. It is recorded in the transfer's status history and audit trail with the subject from the bearer token. This endpoint requires the `transfers:repair` permission and a user token.
      operationId: repairTransferLedgerScope
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: transferId
          in: path
          required: true
          description: The unique identifier of the transfer.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        description: The ledger that the operator asserts, the justification, and an optional evidence reference.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RepairTransferLedgerScopeRequest'
      responses:
        '200':
          description: Indicates that the transfer's ledger scope was repaired.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RepairTransferLedgerScopeResponse'
        '400':
          description: |-
            BTF-0001: invalid `transferId`, missing idempotency key, an absent or malformed `ledgerId`, a `reason` outside 10 to 500 characters, or an `evidenceReference` longer than 200 characters.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |-
            BTF-0405: the caller lacks the `transfers:repair` permission. BTF-0406: the bearer token is not a person (a service account or a token of another type), so this by-hand write cannot be attributed to an operator. No grant fixes this; call the endpoint with a user token. A user token whose owner or subject is only whitespace is refused with 401 BTF-0401 before this check.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '404':
          description: |-
            BTF-0200: transfer not found in this organization. BTF-0500: CRM does not know the transfer's account. BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-0113: the transfer carries no defect to repair: its ledger is already set, or another operator repaired it first.

            BTF-0114: CRM reports a different ledger for this transfer's account. Nothing was written, and neither ledger is echoed in the response.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          description: 'BTF-9000: internal server error. BTF-9006: unexpected empty response from the service layer.'
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '502':
          description: 'BTF-0501: CRM returned a response that could not be parsed. Nothing was written.'
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '503':
          description: |-
            BTF-9005, BTF-9007, BTF-9008: a dependency or the repository is unavailable. BTF-0502, BTF-0503: CRM is unreachable or rejects this plugin's credentials, so the ledger could not be confirmed.

            BTF-9016: this tenant's database cannot record the repair's history row, and nothing was written. Apply the pending migrations to this tenant and send the identical request again under a new `X-Idempotency` key.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.

            BTF-9017: the tenant organization list is absent or unreadable. Retry with the same idempotency key once it is configured.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/repair-transfer-ledger-scope
  /v1/transfers/dashboard/summary:
    get:
      tags:
        - Transfers API
      summary: Get dashboard summary
      description: |-
        Returns aggregated transfer counters and operational health snapshot for dashboards. The `window` parameter sets the period that the counters cover. `byReconciliationStatus`, `health.processingCount`, and `health.pendingCount` count the current state and ignore the window.
      operationId: getDashboardSummary
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - name: window
          in: query
          required: false
          description: The period that the counters cover.
          schema:
            type: string
            enum: [24h, 7d, 30d, all]
            default: 24h
        - name: refresh
          in: query
          required: false
          description: When `true`, asks for fresh numbers instead of the cached snapshot. The service can still answer with the cached snapshot when another refresh of the same window ran recently.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Dashboard summary returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardSummaryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/get-dashboard-summary
  /v1/transfers/reconciliation/pending:
    get:
      tags:
        - Transfers API
      summary: List pending reconciliation transfers
      description: Lists the transfers that still need a reconciliation action, oldest first.
      operationId: listPendingReconciliation
      parameters:
        - $ref: '#/components/parameters/XOrganizationId'
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Maximum number of records to return (1–200, default 50).
        - name: reconciliationStatus
          in: query
          required: false
          schema:
            type: string
            enum: [AWAITING, MANUAL_REVIEW, REVERSING]
          description: |-
            Lists only the transfers in this reconciliation state. Omit it to list every transfer that still needs an action.

            - `AWAITING`: the worker still queries the settlement.
            - `MANUAL_REVIEW`: a person must decide. This includes the transfers stored as `EXHAUSTED`.
            - `REVERSING`: an inbound chargeback held on the ledger, retried every cycle.
      responses:
        '200':
          description: Pending reconciliation list returned successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    description: Transfers that still need a reconciliation action.
                    items:
                      type: object
                      properties:
                        transferId:
                          type: string
                          format: uuid
                          example: '019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d'
                        type:
                          type: string
                          example: TED_OUT
                        status:
                          type: string
                          example: PROCESSING
                        controlNumber:
                          type: string
                          nullable: true
                          example: '202602010001'
                        flaggedAt:
                          type: string
                          format: date-time
                          example: '2026-02-01T15:35:00-03:00'
                        failureCode:
                          type: string
                          example: JD_UNAVAILABLE
                        failureClass:
                          type: string
                          example: unknown_state
                        counterpartySystem:
                          type: string
                          example: JD_SPB
                        reconciliationStatus:
                          type: string
                          enum: [AWAITING, EXHAUSTED, MANUAL_REVIEW, REVERSING]
                          description: The reconciliation state of the transfer, as stored. `EXHAUSTED` has the same meaning as `MANUAL_REVIEW`.
                          example: MANUAL_REVIEW
                        reconciliationAttempts:
                          type: integer
                          description: The number of settlement queries already sent. Always present, zero included.
                          example: 5
                        lastReconciliationAttemptAt:
                          type: string
                          format: date-time
                          description: The time of the most recent settlement query, when one was sent.
                          example: '2026-02-01T15:40:00Z'
                        settlementRejectionCode:
                          type: string
                          description: The rejection code of the clearing house, verbatim, for example `AAC90` or `ALN01`. Absent when there is none.
                          example: ALN01
                  failureConcentration:
                    type: array
                    description: Breakdown of failure codes across pending items.
                    items:
                      type: object
                      properties:
                        failureCode:
                          type: string
                          example: JD_UNAVAILABLE
                        count:
                          type: integer
                          example: 12
                  pagination:
                    type: object
                    description: Pagination metadata.
                    properties:
                      limit:
                        type: integer
                        description: Maximum number of pending transfers requested.
                        example: 50
                      returned:
                        type: integer
                        description: Number of pending transfers returned in this response.
                        example: 25
                      hasNextPage:
                        type: boolean
                        description: Whether the returned page filled the requested limit, indicating more items may exist.
                        example: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: |-
            BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-pending-reconciliation
  /v1/transfers/reconciliation/tick:
    post:
      tags:
        - Transfers API
      summary: Trigger reconciliation sweep
      description: |-
        Forces one immediate reconciliation cycle for the current tenant. This operator endpoint runs the same reconciliation sweep used by the scheduled background worker. Repeated calls with the same `X-Idempotency` key replay the cached response.
      operationId: triggerReconciliationTick
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
      responses:
        '200':
          description: Reconciliation sweep triggered successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerReconciliationResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Indicates that the reconciliation worker or tenant configuration was not found.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '409':
          description: |-
            BTF-9009: a reconciliation sweep is already in progress. Retry after the `Retry-After` delay.

            BTF-0016: a request under this `X-Idempotency` key still holds the in-flight lease, because it is still running or because it already ended 5xx and recorded no outcome. `Retry-After` carries a poll interval, not the lease. While the first request is still running, resend the identical request after that interval; the answer replays from the first request as soon as it finishes. If an earlier request under this key answered 5xx without `X-Idempotency-Fenced: true`, no answer will ever be served from it: stop resending, read the resource this route acts on, and send the work under a new `X-Idempotency` key only if it did not land. The detail also names the 15-minute bound after which the lease lapses and a resend runs the work a second time; a poll still unanswered by then ends the same way.

            BTF-0018: the request under this `X-Idempotency` key already completed, and its response was too large to store for replay. Read the resource. Do not resend under a new key, because that runs the work a second time. This answer carries no `Retry-After`, because no wait produces the missing receipt.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
              examples:
                busy:
                  summary: Sweep already in progress
                  value:
                    type: https://errors.lerian.studio/v1/BTF-9009
                    title: Conflict
                    status: 409
                    detail: reconciliation cycle already in progress
                    code: BTF-9009
                    category: transient
                    requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                    service: plugin
        '422':
          $ref: '#/components/responses/IdempotencyKeyRefused'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/IdempotentInternalServerError'
        '503':
          description: |-
            BTF-9005: a dependency of the reconciliation sweep is unavailable.

            BTF-9000: the idempotency store was unavailable before the handler ran, so nothing ran. Send the same request again under the same `X-Idempotency` key.

            BTF-0019: the work under this `X-Idempotency` key ran, and neither its response nor its outcome fence could be stored, so the key protects nothing and a resend can run the operation a second time. Read the resource before you send anything else.

            BTF-0020: the work under this `X-Idempotency` key ran and its response could not be stored for replay, but the key is still held. This answer is not the outcome of that work. Read the resource, and resend under a new key only if the work did not land.
          headers:
            X-Idempotency-Fenced:
              $ref: '#/components/headers/XIdempotencyFenced'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'
        '504':
          $ref: '#/components/responses/IdempotentGatewayTimeout'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/trigger-reconciliation-sweep
  /v1/transfers/routing/preflight:
    get:
      tags:
        - Transfers API
      summary: Check Ledger Routing Readiness
      description: |-
        Use this endpoint to check whether this tenant is ready for ledger-scoped routing. The report lists one line per Midaz organization, ledger, and transfer type:

        - Every tuple that has carried a transfer, whatever its outcome. `transferCount` measures traffic, not settlement.
        - Every tuple that the chargeback path demands although nothing has transferred on it and no binding addresses it (`transferCount` 0): the inbound tuple that every `TED_OUT` ledger owes for the refund (`derivedFrom: ted_out_refund`), the outbound tuple that every `TED_IN` ledger owes for the compensation (`derivedFrom: ted_in_compensation`), and the `CROSS_CORE_RETURN_IN` tuple that every `CROSS_CORE_OUT` ledger owes for a return from the other core (`derivedFrom: cross_core_return`).
        - One organization-level line per Midaz organization that the tenant registered to receive inbound TED and that has no inbound binding at any ledger (`derivedFrom: ispb_onboarded_organization`). `ledgerId` is empty, because the recipient account record decides the ledger when the credit arrives. Any inbound line for that organization answers the demand, and the line disappears.

        These tuples are demanded of a ledger that the configuration only declares, in either mode, as well as of one that has carried traffic. Once a binding answers the demand, the line is reported as a configured scope with no `derivedFrom`, and it is still judged. `ready` is `true` only when every line clears. A declared scope, and the chargeback counterpart of a declared binding, are judged like any other line, because a binding is a promise about money that the operator intends to move, and a route that does not verify on it holds or refuses that ledger's first transfer. An explicit `mode: omit` answers any line, because it posts with the route fields left off, and a chargeback against money that landed unrouted posts back unrouted. The one pairing that it cannot express, a ledger that routes its incoming credits while it declares their compensation unrouted, is refused when the configuration is read and never reaches this report.

        The report also lists the bindings that the configuration carries, each with whether a binding covers it and whether the accounting routes it names exist inside that exact scope in Midaz. `legacyPendingInitiations` counts the live initiations still pending from before ledger-scoped routing, which this service answers with 409 (BTF-0112). `readbackBudgetExhausted` is `false` for every configuration that the contract accepts. A `true` value means a service defect: report it instead of retrying. The scopes past the budget then read `unavailable`.

        This is a read-only administrative endpoint. It never writes the configuration and never moves money. It does not require the `X-Organization-Id` header. In multi-tenant deployments the report is scoped to the caller's resolved tenant.
      operationId: getLedgerRoutingReadiness
      responses:
        '200':
          description: Indicates that the readiness report was returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LedgerRoutingReadinessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: |-
            BTF-9005: the report readers are not configured in this deployment. BTF-9015: a routing configuration could not be read, either the ledger bindings or the organizations registered to receive inbound TED; the `systemplaneKey` member names which one. BTF-9007: the scan of this tenant's transfers timed out. BTF-9008: the database was unreachable.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDocument'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/check-ledger-routing-readiness
  /v1/webhooks:
    post:
      tags:
        - Webhooks
      summary: Create a Webhook Registration
      description: |-
        Use this endpoint to register a tenant-scoped webhook endpoint that receives transfer event notifications. The registration is scoped to the caller's tenant, derived from the bearer JWT (`tenantId` claim), never from a request header.

        The server generates the `signingSecret` and returns it **exactly once** in this response. Store it securely — it is never returned again. Use it to verify the signature on every delivered event. To obtain a new secret, call the rotate endpoint.

        Use the `X-Idempotency` header for guaranteed deduplication. Repeated calls with the same key replay the cached response.
      operationId: createWebhook
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
            examples:
              create:
                summary: Subscribe to completed transfers
                value:
                  name: Operations webhook
                  endpointUrl: https://hooks.example.com/transfer-events
                  enabled: true
                  eventTypes:
                    - transfer.completed
                    - transfer.rejected
      responses:
        '201':
          description: |-
            Indicates that the webhook registration was created. The `signingSecret` is returned only in this response.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateWebhookResponse'
              examples:
                created:
                  summary: Registration created with signing secret
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    name: Operations webhook
                    endpointUrl: https://hooks.example.com/transfer-events
                    enabled: true
                    eventTypes:
                      - transfer.completed
                      - transfer.rejected
                    signingSecret: whsec_example_not_a_real_secret
                    createdAt: '2026-02-01T15:30:00Z'
                    updatedAt: '2026-02-01T15:30:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/WebhookConflict'
        '422':
          $ref: '#/components/responses/WebhookValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      x-mint:
        href: /pt/reference/interfaces/ted-jd/create-webhook
    get:
      tags:
        - Webhooks
      summary: List Webhook Registrations
      description: |-
        Use this endpoint to retrieve a paginated list of webhook registrations for the caller's tenant. Set `enabled=true` to return only enabled registrations. Signing secrets are never included in this response.
      operationId: listWebhooks
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of registrations to return.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of registrations to skip for pagination.
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: enabled
          in: query
          required: false
          description: When true, returns only enabled registrations.
          schema:
            type: boolean
      responses:
        '200':
          description: Indicates that the matching webhook registrations are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListWebhooksResponse'
              examples:
                list:
                  summary: One registration returned
                  value:
                    items:
                      - id: 550e8400-e29b-41d4-a716-446655440000
                        name: Operations webhook
                        endpointUrl: https://hooks.example.com/transfer-events
                        enabled: true
                        eventTypes:
                          - transfer.completed
                        createdAt: '2026-02-01T15:30:00Z'
                        updatedAt: '2026-02-01T15:30:00Z'
                    pagination:
                      limit: 50
                      offset: 0
                      returned: 1
                      hasNextPage: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-webhooks
  /v1/webhooks/event-types:
    get:
      tags:
        - Webhooks
      summary: List Webhook Event Types
      description: |-
        Use this endpoint to retrieve the deterministic inventory of event types accepted in webhook registrations. Use these values to populate the `eventTypes` field when creating or patching a registration.
      operationId: listWebhookEventTypes
      responses:
        '200':
          description: Indicates that the supported event types are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEventTypesResponse'
              examples:
                eventTypes:
                  summary: Supported event types
                  value:
                    eventTypes:
                      - payment_initiation.created
                      - transfer.completed
                      - transfer.rejected
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-webhook-event-types
  /v1/webhooks/{webhookId}:
    get:
      tags:
        - Webhooks
      summary: Get a Webhook Registration
      description: |-
        Use this endpoint to retrieve a single webhook registration by ID. Only returns registrations belonging to the caller's tenant. The signing secret is never included in this response.
      operationId: getWebhook
      parameters:
        - name: webhookId
          in: path
          required: true
          description: The unique identifier of the webhook registration.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Indicates that the webhook registration was found and its details are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookRegistrationResponse'
              examples:
                found:
                  summary: Registration details
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    name: Operations webhook
                    endpointUrl: https://hooks.example.com/transfer-events
                    enabled: true
                    eventTypes:
                      - transfer.completed
                    createdAt: '2026-02-01T15:30:00Z'
                    updatedAt: '2026-02-01T15:30:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/WebhookNotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      x-mint:
        href: /pt/reference/interfaces/ted-jd/get-webhook
    patch:
      tags:
        - Webhooks
      summary: Patch a Webhook Registration
      description: |-
        Use this endpoint to update one or more fields of a webhook registration. Only the fields present in the request body are changed. Sending `eventTypes` as `null` clears the filter so the registration receives every event type. Only registrations belonging to the caller's tenant can be patched.

        Use the `X-Idempotency` header for guaranteed deduplication.
      operationId: patchWebhook
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: webhookId
          in: path
          required: true
          description: The unique identifier of the webhook registration to patch.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchWebhookRequest'
            examples:
              disable:
                summary: Disable a registration
                value:
                  enabled: false
              changeEvents:
                summary: Narrow the event filter
                value:
                  eventTypes:
                    - transfer.completed
      responses:
        '200':
          description: |-
            Indicates that the webhook registration was updated.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookRegistrationResponse'
              examples:
                patched:
                  summary: Registration updated
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    name: Operations webhook
                    endpointUrl: https://hooks.example.com/transfer-events
                    enabled: false
                    eventTypes:
                      - transfer.completed
                    createdAt: '2026-02-01T15:30:00Z'
                    updatedAt: '2026-02-01T16:00:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/WebhookNotFound'
        '409':
          $ref: '#/components/responses/WebhookConflict'
        '422':
          $ref: '#/components/responses/WebhookValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      x-mint:
        href: /pt/reference/interfaces/ted-jd/update-webhook
    delete:
      tags:
        - Webhooks
      summary: Delete a Webhook Registration
      description: |-
        Use this endpoint to soft-delete a webhook registration. After deletion the endpoint stops receiving events. Only registrations belonging to the caller's tenant can be deleted.

        Use the `X-Idempotency` header for guaranteed deduplication.
      operationId: deleteWebhook
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: webhookId
          in: path
          required: true
          description: The unique identifier of the webhook registration to delete.
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Indicates that the webhook registration was soft-deleted. No content is returned.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/WebhookNotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/delete-webhook
  /v1/webhooks/{webhookId}/signing-secret/rotate:
    post:
      tags:
        - Webhooks
      summary: Rotate a Webhook Signing Secret
      description: |-
        Use this endpoint to generate a new server-owned signing secret for a webhook registration. The new `signingSecret` is returned **only** in this response — store it securely and update your signature verification. The previous secret stops being valid. Only registrations belonging to the caller's tenant can be rotated.

        Use the `X-Idempotency` header for guaranteed deduplication.
      operationId: rotateWebhookSigningSecret
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: webhookId
          in: path
          required: true
          description: The unique identifier of the webhook registration whose secret will be rotated.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: |-
            Indicates that a new signing secret was generated. The `signingSecret` is returned only in this response.

            Repeated calls with the same `X-Idempotency` key replay the cached response.

            See [Retries and idempotency](/pt/reference/retries-idempotency) for more details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotateWebhookSigningSecretResponse'
              examples:
                rotated:
                  summary: New signing secret returned
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    name: Operations webhook
                    endpointUrl: https://hooks.example.com/transfer-events
                    enabled: true
                    eventTypes:
                      - transfer.completed
                    signingSecret: whsec_example_rotated_not_a_real_secret
                    createdAt: '2026-02-01T15:30:00Z'
                    updatedAt: '2026-02-01T16:30:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/WebhookNotFound'
        '409':
          $ref: '#/components/responses/WebhookConflict'
        '422':
          $ref: '#/components/responses/WebhookValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/rotate-webhook-signing-secret
  /v1/webhooks/dlq:
    get:
      tags:
        - Webhook DLQ
      summary: List DLQ Messages
      description: |-
        Use this endpoint to list messages currently in the webhook dead-letter queue, scoped to the caller's tenant. The tenant is derived from the bearer JWT (`tenantId` claim), never from a request header.

        This route is only mounted when authentication is enabled and a DLQ inspector is configured. Otherwise it is not exposed and any request is rejected with 503.
      operationId: listDLQMessages
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of messages to return.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of messages to skip for pagination.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Indicates the DLQ messages page was returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DLQListResponse'
              examples:
                page:
                  summary: One DLQ message returned
                  value:
                    messages:
                      - messageId: 019c96a0-d1d2-7e3f-4a5b-6c7d8e9f0a1b
                        routingKey: webhook.transfer.completed
                        deathReason: rejected
                        failureReason: HTTP 500 from subscriber
                        tenantId: 11111111-1111-1111-1111-111111111111
                        transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
                        eventType: transfer.completed
                        correlationId: 019c96a0-cc10-7abc-d1e2-3f4a5b6c7d8e
                        retryCount: 5
                        manualRetryCount: 1
                        timestamp: '2026-02-01T15:35:00-03:00'
                    totalCount: 1
                    returnedCount: 1
                    returned: 1
                    hasNextPage: false
                    hasMore: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/list-webhook-dlq-messages
  /v1/webhooks/dlq/{messageId}/retry:
    post:
      tags:
        - Webhook DLQ
      summary: Retry DLQ Message
      description: |-
        Use this endpoint to re-publish a specific DLQ message back to the main webhook processing queue. Only messages belonging to the caller's tenant are eligible. The tenant is derived from the bearer JWT (`tenantId` claim).

        This route is only mounted when authentication is enabled and a DLQ inspector is configured. Otherwise it is not exposed and any request is rejected with 503.
      operationId: retryDLQMessage
      parameters:
        - $ref: '#/components/parameters/XIdempotencyKey'
        - name: messageId
          in: path
          required: true
          description: The DLQ message identifier to retry.
          schema:
            type: string
      responses:
        '200':
          description: |-
            Indicates that the DLQ message was re-published to the main queue.

          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DLQRetryResponse'
              examples:
                retried:
                  summary: Message re-published
                  value:
                    status: retried
                    messageId: 019c96a0-d1d2-7e3f-4a5b-6c7d8e9f0a1b
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/DLQMessageNotFound'
        '409':
          $ref: '#/components/responses/DLQMaxRetriesExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

      x-mint:
        href: /pt/reference/interfaces/ted-jd/retry-webhook-dlq-message
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Bearer token authentication. The tenantId is derived from the bearer token or authenticated request context and is not supplied through X-Organization-Id.
    OAuth2ClientCredentials:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: /v1/login/oauth/access_token
          scopes:
            api: TED transfer API access
    OAuth2Password:
      type: oauth2
      flows:
        password:
          tokenUrl: /v1/login/oauth/access_token
          scopes:
            api: TED transfer API access

  parameters:
    XOrganizationId:
      name: X-Organization-Id
      in: header
      required: true
      description: |-
        Midaz organization scope for the request, used for downstream CRM, Fees, and Midaz calls. Required on org-scoped transfer routes in every deployment mode; a missing or non-UUID value returns 400. This is not the tenant identifier — tenantId is derived from the bearer JWT or authenticated context, never from this header. Background workers (TED IN poller, reconciliation) have no request header and, in single-tenant mode, fall back to the deployment's `ORGANIZATION_ID` env.
      schema:
        type: string
        format: uuid
      example: 019c96a0-0a98-7287-9a31-786e0809c769

    XIdempotencyKey:
      name: X-Idempotency
      in: header
      required: true
      description: |-
        Required idempotency key for safe retries. Use a UUID v4 or a unique business identifier, made of letters, digits, `.`, `_`, and `-`. If the same key is sent again and the original request was already processed, the cached response is returned.

        See [Retries and idempotency](/pt/reference/retries-idempotency) for details.
      schema:
        type: string
        maxLength: 255
        pattern: '^[a-zA-Z0-9._-]+$'
      example: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b


  schemas:
    # Request Schemas
    InitiateTransferRequest:
      type: object
      required:
        - senderAccountId
        - recipient
        - amount
        - purpose
      properties:
        senderAccountId:
          type: string
          format: uuid
          description: The Midaz account ID of the sender.
          example: 019c96a0-0c0c-7221-8cf3-13313fb60081
        recipient:
          $ref: '#/components/schemas/RecipientDetails'
        amount:
          type: number
          format: decimal
          minimum: 0.01
          maximum: 999999.99
          description: The transfer amount in BRL.
          example: 1000.50
        purpose:
          type: string
          pattern: '^[0-9]{1,4}$'
          maxLength: 4
          description: |
            BACEN SPB FinlddCli code identifying the transfer purpose. Must be 1–4 ASCII digits.

            Common FinlddCli codes:
              * `1` — Pagamento de Impostos, Tributos e Taxas
              * `3` — Pagamentos de Dividendos
              * `10` — Crédito em Conta
              * `100` — Depósito Judicial

            Refer to the BACEN _Dicionário de Domínios_ (`FinlddCli`) for the full table of active codes.

            **Note:** the plugin currently accepts FinlddCli values of up to 4 digits. 5-digit codes from the BACEN catalog (such as `99999` — _Outros_) are not yet supported and will return a validation error.
          example: "10"
        description:
          type: string
          maxLength: 140
          description: A free-text description of the transfer.
          example: Payment for services
        metadata:
          type: object
          additionalProperties: true
          description: Custom metadata as key-value pairs.
          example:
            invoiceId: INV-2024-001
            orderId: ORD-2024-123
        feeContext:
          type: string
          maxLength: 50
          pattern: '^[a-z][a-z0-9_]{0,49}$'
          description: A fee label that you choose. A fee package on the payer's ledger selects on it. The ledger postings of the transfer carry it. Between two ledgers, each ledger prices its own part. The label is accepted only when the ledger prices fees natively.
          example: ted_salario

    ProcessTransferRequest:
      type: object
      oneOf:
        - required:
            - initiationId
          not:
            anyOf:
              - required: [signingArtifactId]
              - required: [payloadHash]
              - required: [signature]
        - required:
            - initiationId
            - signingArtifactId
            - payloadHash
            - signature
      properties:
        initiationId:
          type: string
          format: uuid
          description: The initiation ID returned by the Initiate Transfer endpoint. Expires after 24 hours.
          example: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b
        signingArtifactId:
          type: string
          format: uuid
          description: Identifier of the frozen signing artifact returned by Prepare Transfer Signature.
          example: 019c96a0-bb10-7def-a1b2-3c4d5e6f7a8b
        payloadHash:
          type: string
          description: SHA-256 hash of the canonical payload that was signed.
          example: 8f0c7f5c1c7b4d9f2f5c7d1a0d4c9a2b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f
        signature:
          type: string
          description: Base64-encoded signature for tenants that sign outside the plugin.
          example: base64-signature-here

    # Response Schemas
    InitiateTransferResponse:
      type: object
      required:
        - initiationId
        - feeAmount
        - totalAmount
        - estimatedCompletionAt
        - expiresAt
        - status
      properties:
        initiationId:
          type: string
          format: uuid
          description: The unique initiation ID. Use this value in the Process Transfer endpoint to confirm the transfer.
          example: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b
        feeAmount:
          type: number
          format: decimal
          description: The calculated fee amount. Returns 0.00 if fees are disabled for this organization.
          example: 1.50
        totalAmount:
          type: number
          format: decimal
          description: "The amount debited from the sender's account: the transfer amount plus any fee charged on top."
          example: 1002.00
        estimatedCompletionAt:
          type: string
          format: date-time
          description: The estimated time when the transfer will be completed.
          example: '2026-02-01T18:00:00-03:00'
        expiresAt:
          type: string
          format: date-time
          description: The time when this initiation expires, 24 hours after creation.
          example: '2026-02-02T15:30:00-03:00'
        status:
          $ref: '#/components/schemas/InitiationStatus'
        feeEntries:
          type: array
          description: Itemized fee breakdown. Each entry corresponds to one fee charged during the transfer.
          items:
            type: object
            properties:
              label:
                type: string
                description: Fee identifier label.
                example: ted_transfer_fee
              amount:
                type: number
                format: decimal
                description: Fee amount.
                example: 1.50
              creditAccount:
                type: string
                description: Account that receives the fee credit.
                example: platform-fee-account
              sourceAccount:
                type: string
                description: Account debited for the fee.
                example: sender-account
              isDeductible:
                type: boolean
                description: Whether the fee is deducted from the transfer amount (true) or added on top (false).
                example: false
        packageAppliedId:
          type: string
          format: uuid
          nullable: true
          description: ID of the fee package applied to this transfer, if any.
          example: '019c96a0-ad10-7fab-c1d2-3b4c5d6e7f8a'
        recipientLedgerId:
          type: string
          format: uuid
          description: The recipient's Midaz ledger on a P2P between two ledgers of one organization. Absent on a P2P inside one ledger and on every TED.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e03

    ProcessTransferResponse:
      type: object
      required:
        - transferId
        - confirmationNumber
        - status
        - feeAmount
        - totalAmount
      properties:
        transferId:
          type: string
          format: uuid
          description: The unique transfer ID. Use this value to track the transfer status.
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        confirmationNumber:
          type: string
          maxLength: 20
          description: A human-readable confirmation number. Format is YYYYMMDD followed by a short hex suffix derived from the transfer ID (e.g. 20260201aa10bc3d4e5f).
          example: '20260201001'
        status:
          allOf:
            - $ref: '#/components/schemas/TransferStatus'
          description: The current transfer status. For TED_OUT transfers, the status immediately after processing is always PROCESSING.
        feeAmount:
          type: number
          format: decimal
          description: The fee amount as calculated during initiation.
          example: 1.50
        totalAmount:
          type: number
          format: decimal
          description: "The amount debited from the sender's account: the transfer amount plus any fee charged on top."
          example: 1002.00
        packageAppliedId:
          type: string
          format: uuid
          nullable: true
          description: ID of the fee package applied to this transfer, if any.
          example: '019c96a0-ad10-7fab-c1d2-3b4c5d6e7f8a'
        recipientLedgerId:
          type: string
          format: uuid
          description: The recipient's Midaz ledger on a P2P between two ledgers of one organization. Absent on a P2P inside one ledger and on every TED.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e03
        ledgerGroupId:
          type: string
          format: uuid
          description: The Midaz ledger group that a P2P between two ledgers posted as. Absent when the expiry recovery completed the transfer.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e10

    Transfer:
      type: object
      required:
        - transferId
        - type
        - status
        - organizationId
        - ledgerId
        - senderAccountId
        - recipient
        - amount
        - feeAmount
        - totalAmount
        - createdAt
        - updatedAt
        - statusHistory
        - reconciliationAttempts
      properties:
        transferId:
          type: string
          format: uuid
          description: The unique identifier of the transfer.
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        type:
          $ref: '#/components/schemas/TransferType'
        status:
          $ref: '#/components/schemas/TransferStatus'
        organizationId:
          type: string
          format: uuid
          description: The Midaz organization that the transfer is filed under. It equals the `X-Organization-Id` of the request.
          example: 019c96a0-0a98-7287-9a31-786e0809c769
        ledgerId:
          type: string
          format: uuid
          description: The Midaz ledger that the transfer is filed under. On a P2P between two ledgers, the payer's ledger. Always present. The all-zeroes UUID means that the scope is unrepaired. See Repair Transfer Ledger Scope.
          example: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
        recipientLedgerId:
          type: string
          format: uuid
          description: The recipient's Midaz ledger on a P2P between two ledgers of one organization. Absent on a P2P inside one ledger and on every TED.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e03
        ledgerGroupId:
          type: string
          format: uuid
          description: The Midaz ledger group that a P2P between two ledgers posted as. Absent before the posting, when the expiry recovery completed the transfer, and on a P2P return.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e10
        originalTransferId:
          type: string
          format: uuid
          nullable: true
          description: On a return, the transfer that it returns. A `TED_IN` devolution refund names the TED OUT that it compensates, a `TED_OUT` voluntary return names the TED IN that it sends back, and a `P2P` return names the P2P that it reverses. On a TED IN return and a P2P return, `recipient` carries only the ISPB. Read the counterparty from the original transfer.
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        devolutionCode:
          type: string
          nullable: true
          description: BACEN devolution reason code, present only when the transfer is a devolution.
          example: "18"
        feeContext:
          type: string
          description: The fee label that the initiation carried. Absent when it carried none.
          example: ted_salario
        senderAccountId:
          type: string
          format: uuid
          description: "UUID of the sender account. Always present. For TED IN, where the sender is an external bank with no local account, this is a synthetic identifier deterministically derived from the sender's document."
          example: 019c96a0-0c0c-7221-8cf3-13313fb60081
        recipientAccountId:
          type: string
          format: uuid
          nullable: true
          description: UUID of the recipient account, when the recipient is represented as a Midaz account.
          example: 019c96a0-ac10-7efa-b1c2-2a3b4c5d6e7f
        recipient:
          $ref: '#/components/schemas/RecipientDetails'
        amount:
          type: number
          format: decimal
          description: The transfer amount without the fee.
          example: 1000.50
        feeAmount:
          type: number
          format: decimal
          description: The fee charged for this transfer.
          example: 1.50
        totalAmount:
          type: number
          format: decimal
          description: "The amount debited from the sender's account: the transfer amount plus any fee charged on top. For TED IN, the gross amount received."
          example: 1002.00
        confirmationNumber:
          type: string
          nullable: true
          maxLength: 20
          description: The human-readable confirmation code, if available.
          example: '20260201001'
        controlNumber:
          type: string
          nullable: true
          maxLength: 20
          description: The JD SPB control number, present for TED OUT and TED IN transfers.
          example: '202602010001'
        midazTransactionId:
          type: string
          format: uuid
          nullable: true
          description: The corresponding Midaz ledger transaction ID.
          example: 019c96a0-ac10-7efa-b1c2-2a3b4c5d6e7f
        description:
          type: string
          nullable: true
          maxLength: 140
          description: The purpose or description of the transfer.
          example: Payment for services
        metadata:
          type: object
          additionalProperties: true
          description: Metadata that this service records on the transfer, as key-value pairs. On a return, it also carries `returnReasonCode` (on a TED IN return) and `reversalTransactionId`, the ledger transaction that reverses the original, once they are recorded. The metadata that you sent on Initiate Transfer is in `clientMetadata`.
        clientMetadata:
          type: object
          additionalProperties: true
          description: The metadata that you sent on Initiate Transfer, as key-value pairs. Absent when the transfer carries none. A TED IN transfer carries none.
          example:
            invoiceId: INV-2024-001
        createdAt:
          type: string
          format: date-time
          description: The timestamp when the transfer was created.
          example: '2026-02-01T18:30:00Z'
        updatedAt:
          type: string
          format: date-time
          description: The timestamp of the last status update.
          example: '2026-02-01T18:35:00Z'
        completedAt:
          type: string
          format: date-time
          nullable: true
          description: The timestamp when the transfer was completed. Null if not yet completed.
          example: '2026-02-01T18:35:00Z'
        statusHistory:
          type: array
          description: The history of status transitions for this transfer.
          items:
            $ref: '#/components/schemas/StatusHistoryEntry'
        reconciliationStatus:
          type: string
          enum: [AWAITING, RESOLVED, REVERSING, REVERTED, EXHAUSTED, MANUAL_REVIEW]
          description: The reconciliation state of the transfer, as stored. `EXHAUSTED` has the same meaning as `MANUAL_REVIEW`. Absent on a transfer that never entered reconciliation.
          example: MANUAL_REVIEW
        reconciliationAttempts:
          type: integer
          description: The number of settlement queries already sent for this transfer. Always present, zero included.
          example: 0
        lastReconciliationAttemptAt:
          type: string
          format: date-time
          description: The time of the most recent settlement query, when one was sent.
          example: '2026-02-01T15:40:00Z'
        reconciliationReasonCode:
          type: string
          description: Why the transfer entered reconciliation, in the vocabulary of the system that decided it. Absent when no reason is recorded.
          example: JD_UNAVAILABLE
        reconciliationReasonClass:
          type: string
          description: The class of `reconciliationReasonCode`. Absent when no reason is recorded.
          example: transient
        settlementRejectionCode:
          type: string
          description: The rejection code of the clearing house, verbatim, for example `AAC90` or `ALN01`. Absent when there is none.
          example: ALN01

    TriggerReconciliationResponse:
      type: object
      properties:
        outcome:
          type: string
          enum: [success, skipped_operating_window]
          description: Result of the reconciliation sweep. `skipped_operating_window` indicates the sweep did not run because it was triggered outside the operating-hours window.
          example: success
        sweptCount:
          type: integer
          description: Number of transfers processed by the sweep.
          example: 3
        durationMs:
          type: integer
          format: int64
          description: Sweep duration in milliseconds.
          example: 125

    TransferList:
      type: object
      required:
        - items
        - pagination
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/TransferSummary'
        pagination:
          $ref: '#/components/schemas/PaginationInfo'

    TransferSummary:
      type: object
      required:
        - transferId
        - type
        - status
        - organizationId
        - ledgerId
        - senderAccountId
        - amount
        - feeAmount
        - totalAmount
        - createdAt
        - updatedAt
      properties:
        transferId:
          type: string
          format: uuid
          example: 770e8400-e29b-41d4-a716-446655440003
        type:
          $ref: '#/components/schemas/TransferType'
        status:
          $ref: '#/components/schemas/TransferStatus'
        reconciliationStatus:
          type: string
          enum: [AWAITING, RESOLVED, REVERSING, REVERTED, EXHAUSTED, MANUAL_REVIEW]
          description: The reconciliation state of the transfer, as stored. `EXHAUSTED` has the same meaning as `MANUAL_REVIEW`. Absent on a transfer that never entered reconciliation.
          example: MANUAL_REVIEW
        organizationId:
          type: string
          format: uuid
          description: The Midaz organization that the transfer is filed under. It equals the `X-Organization-Id` of the request.
          example: 019c96a0-0a98-7287-9a31-786e0809c769
        ledgerId:
          type: string
          format: uuid
          description: The Midaz ledger that the transfer is filed under. On a P2P between two ledgers, the payer's ledger. Always present. The all-zeroes UUID means that the scope is unrepaired. See Repair Transfer Ledger Scope.
          example: 019c96a0-0b10-7c2d-8e3f-4a5b6c7d8e9f
        recipientLedgerId:
          type: string
          format: uuid
          description: The recipient's Midaz ledger on a P2P between two ledgers of one organization. Absent on a P2P inside one ledger and on every TED.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e03
        ledgerGroupId:
          type: string
          format: uuid
          description: The Midaz ledger group that a P2P between two ledgers posted as. Absent before the posting, when the expiry recovery completed the transfer, and on a P2P return.
          example: 0190a0e2-7b1c-7d3e-9f40-5a6b7c8d9e10
        originalTransferId:
          type: string
          format: uuid
          description: On a return, the transfer that it returns. A `TED_IN` devolution refund names the TED OUT that it compensates, a `TED_OUT` voluntary return names the TED IN that it sends back, and a `P2P` return names the P2P that it reverses. On a TED IN return and a P2P return, `recipient` carries only the ISPB. Read the counterparty from the original transfer.
          example: 770e8400-e29b-41d4-a716-446655440001
        devolutionCode:
          type: string
          description: BACEN devolution reason code, present only when the transfer is a devolution.
          example: "18"
        senderAccountId:
          type: string
          format: uuid
          description: UUID of the sender account. For TED IN, a synthetic identifier derived from the sender's document.
          example: 019c96a0-0c0c-7221-8cf3-13313fb60081
        recipientAccountId:
          type: string
          format: uuid
          description: UUID of the recipient account, when the recipient is represented as a Midaz account.
          example: 019c96a0-ac10-7efa-b1c2-2a3b4c5d6e7f
        midazTransactionId:
          type: string
          format: uuid
          description: The corresponding Midaz ledger transaction ID, when one exists.
          example: 019c96a0-ac10-7efa-b1c2-2a3b4c5d6e7f
        amount:
          type: number
          format: decimal
          example: 1000.50
        feeAmount:
          type: number
          format: decimal
          example: 1.50
        totalAmount:
          type: number
          format: decimal
          description: "The amount debited from the sender's account: the transfer amount plus any fee charged on top. For TED IN, the gross amount received."
          example: 1002.00
        createdAt:
          type: string
          format: date-time
          example: '2026-02-01T15:30:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Timestamp of the last status transition for this transfer.
          example: '2026-02-01T15:35:00Z'

    CancelTransferResponse:
      type: object
      required:
        - transferId
        - status
        - cancelledAt
      properties:
        transferId:
          type: string
          format: uuid
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        status:
          type: string
          enum: [CANCELLED]
          example: CANCELLED
        cancelledAt:
          type: string
          format: date-time
          example: '2026-02-01T18:35:00Z'

    ReturnTransferRequest:
      type: object
      additionalProperties: false
      properties:
        externalReference:
          type: string
          maxLength: 128
          pattern: '^[A-Za-z0-9._:/-]{1,128}$'
          description: The reference of your system for this return. The plugin records it on the return and in its audit, and sends it back in the answer. Required when the token belongs to an application. Do not put personal data in it.
          example: crm-ticket-88231

    ReturnIncomingTransferResponse:
      type: object
      description: The return of a TED IN.
      properties:
        transferId:
          type: string
          format: uuid
          description: The return, a new `TED_OUT` transfer.
          example: 9b2f1c7e-4d3a-4f1e-9c2b-7a6d5e4f3a21
        originalTransferId:
          type: string
          format: uuid
          description: The TED IN that this return sends back.
          example: 660e8400-e29b-41d4-a716-446655440002
        type:
          type: string
          enum: [TED_OUT]
          example: TED_OUT
        status:
          type: string
          enum: [PROCESSING]
          description: The final status arrives later.
          example: PROCESSING
        reconciliationStatus:
          type: string
          enum: [AWAITING]
          description: Present when nothing proves whether the STR0010 left. Reconciliation then decides.
          example: AWAITING
        amount:
          type: number
          format: decimal
          description: The gross amount of the TED IN, sent in the STR0010. It is not the net amount credited to the recipient.
          example: 1500.00
        returnReasonCode:
          type: string
          description: The return reason code sent in the STR0010. Always `70`.
          example: '70'
        controlNumber:
          type: string
          description: The control number (NumCtrlIF) of the STR0010 sent.
          example: 20261014a1b2c3d4e5f6
        originalClearingControlNumber:
          type: string
          description: The STR control number (NumCtrlSTR) of the original STR0008.
          example: STR20261013033030760
        requestedVia:
          type: string
          enum: [user, application, unknown]
          description: The channel that the token names.
          example: application
        externalReference:
          type: string
          description: The `externalReference` of the request, when one was sent.
          example: crm-ticket-88231
        createdAt:
          type: string
          format: date-time
          example: '2026-10-14T15:30:00Z'

    ReturnInternalTransferResponse:
      type: object
      description: The return of a P2P.
      properties:
        transferId:
          type: string
          format: uuid
          description: The return, a new `P2P` transfer.
          example: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
        originalTransferId:
          type: string
          format: uuid
          description: The P2P that this return reverses. It stays `COMPLETED`.
          example: 770e8400-e29b-41d4-a716-446655440003
        type:
          type: string
          enum: [P2P]
          example: P2P
        status:
          type: string
          enum: [COMPLETED]
          example: COMPLETED
        amount:
          type: number
          format: decimal
          description: The amount of the original P2P.
          example: 250.00
        ledgerId:
          type: string
          format: uuid
          description: The ledger of the account that the return debits, which received the original.
          example: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
        recipientLedgerId:
          type: string
          format: uuid
          nullable: true
          description: The ledger of the account that the return credits, which paid the original. `null` on a P2P inside one ledger.
          example: b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e
        reversalTransactionId:
          type: string
          format: uuid
          description: The ledger transaction that reverses the original transaction. It is on the ledger of the account that paid the original.
          example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f
        requestedVia:
          type: string
          enum: [user, application, unknown]
          description: The channel that the token names.
          example: user
        externalReference:
          type: string
          description: The `externalReference` of the request that created the return, when one was sent.
          example: crm-ticket-88231
        createdAt:
          type: string
          format: date-time
          example: '2026-10-22T14:00:00Z'
        completedAt:
          type: string
          format: date-time
          example: '2026-10-22T14:00:05Z'

    PrepareSigningRequest:
      type: object
      required:
        - initiationId
      properties:
        initiationId:
          type: string
          format: uuid
          description: The initiation ID returned by the Initiate Transfer endpoint.
          example: 019c96a0-aa10-7abc-d1e2-8c9d0e1f2a3b

    PrepareSigningResponse:
      type: object
      required:
        - signingArtifactId
        - transferId
        - controlNumber
        - algorithm
        - payload
        - payloadHash
        - expiresAt
      properties:
        signingArtifactId:
          type: string
          format: uuid
          description: Identifier of the frozen signing artifact. Pass this value to Process Transfer along with the signature.
          example: 019c96a0-bb10-7def-a1b2-3c4d5e6f7a8b
        transferId:
          type: string
          format: uuid
          description: The transfer ID reserved for the eventual TED OUT. Not queryable until Process Transfer succeeds.
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        controlNumber:
          type: string
          maxLength: 20
          description: The JD SPB control number reserved for this transfer.
          example: '202602010001'
        algorithm:
          type: string
          enum: [RSA_PKCS1_V15_SHA256]
          description: The signing algorithm. Pinned per JD SPB contract; any other value is rejected at Process Transfer.
          example: RSA_PKCS1_V15_SHA256
        payload:
          type: string
          description: The canonical STR0008 payload, exactly as it will be submitted to JD SPB.
          example: <STR0008>...</STR0008>
        payloadHash:
          type: string
          description: Hex-encoded SHA-256 digest of the payload. Echo this value at Process Transfer for integrity validation.
          example: 8f0c7f5c1c7b4d9f2f5c7d1a0d4c9a2b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f
        signableContent:
          type: string
          description: The `ConteudoAssinado` extraction for external signers. Present only when an external signing builder is configured.
          example: <ConteudoAssinado>...</ConteudoAssinado>
        expiresAt:
          type: string
          format: date-time
          description: The time when the signing artifact expires. Process Transfer must be called before this timestamp.
          example: '2026-02-02T15:30:00-03:00'

    TEDInPollResponse:
      type: object
      required:
        - outcome
        - processed
      properties:
        outcome:
          type: string
          description: The cycle outcome, for example `success` or `empty`. A value that starts with `skipped_` means that the cycle did not run.
          example: success
        processed:
          type: integer
          minimum: 0
          description: Number of inbound messages drained and processed during this cycle.
          example: 3

    TEDInReplayResponse:
      type: object
      required:
        - outcome
        - replayed
        - held
        - claimed
      properties:
        outcome:
          type: string
          description: The replay outcome, for example `success`, `empty`, or `held`. `held` means that the pass replayed nothing and left at least one inbound credit held.
          example: success
        replayed:
          type: integer
          minimum: 0
          description: Number of persisted backlog rows reprocessed during this pass.
          example: 2
        held:
          type: integer
          minimum: 0
          description: The number of inbound credits that this pass left held, because no ledger binding covers them. They are neither credited nor failed.
          example: 0
        claimed:
          type: integer
          minimum: 0
          description: The number of messages that another process is already carrying, so this pass left them alone. They are replayed when that claim expires.
          example: 0

    TEDInResumeRequest:
      type: object
      description: Optional body for a TED IN poller resume. All fields are optional; an empty body re-arms the poller and leaves the reconciliation gap open.
      properties:
        acknowledge:
          type: boolean
          default: false
          description: When `true`, mark the current latch's reconciliation gap resolved after the poller is re-armed. Defaults to `false` so the gap stays OPEN and remains visible for review.
          example: false
        note:
          type: string
          maxLength: 500
          description: Operator annotation stamped onto the resolved gap when `acknowledge` is `true`. Ignored otherwise.
          example: verified against the JD cabine; no inbound message lost

    TEDInResumeResponse:
      type: object
      required:
        - resumed
        - gapResolved
        - reason
      properties:
        resumed:
          type: boolean
          description: '`true` when a latched or dead poller was re-armed; `false` for the healthy no-op.'
          example: true
        gapResolved:
          type: boolean
          description: '`true` only when `acknowledge` was requested AND the gap row was successfully marked resolved.'
          example: false
        reason:
          type: string
          enum: [receive_state_unknown, panic, auth_failure, not_latched]
          description: The poller state that was resumed from. `not_latched` indicates the idempotent no-op.
          example: receive_state_unknown
        tenantId:
          type: string
          format: uuid
          description: Resolved tenant identifier. Omitted in single-tenant deployments.
          example: 11111111-1111-1111-1111-111111111111

    DLQMessage:
      type: object
      required:
        - messageId
        - routingKey
        - deathReason
        - failureReason
        - tenantId
        - eventType
        - retryCount
        - manualRetryCount
        - timestamp
      properties:
        messageId:
          type: string
          description: Unique DLQ message identifier.
          example: 019c96a0-d1d2-7e3f-4a5b-6c7d8e9f0a1b
        routingKey:
          type: string
          description: Original routing key the message was published with.
          example: webhook.transfer.completed
        deathReason:
          type: string
          description: Reason the broker dead-lettered the message (e.g., `rejected`, `expired`, `maxlen`).
          example: rejected
        failureReason:
          type: string
          description: Last subscriber-side failure detail captured before the message was dead-lettered.
          example: HTTP 500 from subscriber
        tenantId:
          type: string
          format: uuid
          description: Tenant that owns the message.
          example: 11111111-1111-1111-1111-111111111111
        transferId:
          type: string
          format: uuid
          description: Transfer ID associated with the webhook event, when present.
          example: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d
        eventType:
          type: string
          description: Webhook event type (e.g., `transfer.completed`, `transfer.rejected`).
          example: transfer.completed
        correlationId:
          type: string
          description: Correlation ID propagated from the originating request, when present.
          example: 019c96a0-cc10-7abc-d1e2-3f4a5b6c7d8e
        retryCount:
          type: integer
          minimum: 0
          description: Number of automatic delivery attempts before dead-lettering.
          example: 5
        manualRetryCount:
          type: integer
          minimum: 0
          description: Number of manual retries already triggered for this message.
          example: 1
        timestamp:
          type: string
          format: date-time
          description: Time the message was dead-lettered.
          example: '2026-02-01T15:35:00-03:00'

    DLQListResponse:
      type: object
      required:
        - messages
        - totalCount
        - returnedCount
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/DLQMessage'
        totalCount:
          type: integer
          minimum: 0
          description: Tenant-filtered message count visited during this scan. When `hasMore` is true this is a best-effort lower bound on the true DLQ depth for this tenant, not the full count.
          example: 1
        returnedCount:
          type: integer
          minimum: 0
          description: Number of messages in this page after offset and limit are applied.
          example: 1
        returned:
          type: integer
          minimum: 0
          description: Number of messages returned in this page.
          example: 1
        hasNextPage:
          type: boolean
          description: Whether there are more tenant-visible messages beyond this page.
          example: false
        hasMore:
          type: boolean
          description: True when the inspector stopped scanning early after filling the current page.
          example: false
        warning:
          type: string
          description: Set when pagination may be non-deterministic (e.g., concurrent access shifts ordering).
          example: pagination may be non-deterministic under concurrent access

    DLQRetryResponse:
      type: object
      required:
        - status
        - messageId
      properties:
        status:
          type: string
          enum: [retried]
          description: Retry outcome label.
          example: retried
        messageId:
          type: string
          description: Echoes the DLQ message identifier that was re-published.
          example: 019c96a0-d1d2-7e3f-4a5b-6c7d8e9f0a1b

    # Webhook Schemas
    CreateWebhookRequest:
      type: object
      required:
        - name
        - endpointUrl
      properties:
        name:
          type: string
          description: A human-readable name for the registration.
          example: Operations webhook
        endpointUrl:
          type: string
          format: uri
          pattern: '^https://'
          description: The endpoint that receives event deliveries. Must be an HTTPS URL.
          example: https://hooks.example.com/transfer-events
        enabled:
          type: boolean
          description: Whether the registration is active. Defaults to true when omitted.
          example: true
        eventTypes:
          type: array
          maxItems: 64
          description: The event types this endpoint subscribes to. When omitted, the registration receives every event type. Use the List Webhook Event Types endpoint for the accepted values.
          items:
            type: string
          example:
            - transfer.completed
            - transfer.rejected

    PatchWebhookRequest:
      type: object
      description: Only the fields present in the request are updated. Sending `eventTypes` as `null` clears the filter so the registration receives every event type.
      properties:
        name:
          type: string
          description: A human-readable name for the registration.
          example: Operations webhook
        endpointUrl:
          type: string
          format: uri
          pattern: '^https://'
          description: The endpoint that receives event deliveries. Must be an HTTPS URL.
          example: https://hooks.example.com/transfer-events
        enabled:
          type: boolean
          description: Whether the registration is active.
          example: true
        eventTypes:
          type: array
          maxItems: 64
          nullable: true
          description: The event types this endpoint subscribes to. Send `null` to clear the filter and receive every event type.
          items:
            type: string
          example:
            - transfer.completed

    WebhookRegistrationResponse:
      type: object
      required:
        - id
        - name
        - endpointUrl
        - enabled
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the webhook registration.
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          description: The human-readable name of the registration.
          example: Operations webhook
        endpointUrl:
          type: string
          format: uri
          description: The HTTPS endpoint that receives event deliveries.
          example: https://hooks.example.com/transfer-events
        enabled:
          type: boolean
          description: Whether the registration is active.
          example: true
        eventTypes:
          type: array
          description: The event types this endpoint subscribes to. Absent when the registration receives every event type.
          items:
            type: string
          example:
            - transfer.completed
        createdAt:
          type: string
          format: date-time
          description: The timestamp when the registration was created.
          example: '2026-02-01T15:30:00Z'
        updatedAt:
          type: string
          format: date-time
          description: The timestamp of the last update.
          example: '2026-02-01T15:30:00Z'

    CreateWebhookResponse:
      type: object
      allOf:
        - $ref: '#/components/schemas/WebhookRegistrationResponse'
        - type: object
          required:
            - signingSecret
          properties:
            signingSecret:
              type: string
              description: The server-generated signing secret, returned **only** in this response. Store it securely and use it to verify the signature on delivered events.
              example: whsec_example_not_a_real_secret

    RotateWebhookSigningSecretResponse:
      type: object
      allOf:
        - $ref: '#/components/schemas/WebhookRegistrationResponse'
        - type: object
          required:
            - signingSecret
          properties:
            signingSecret:
              type: string
              description: The newly generated signing secret, returned **only** in this response. The previous secret stops being valid.
              example: whsec_example_rotated_not_a_real_secret

    ListWebhooksResponse:
      type: object
      required:
        - items
        - pagination
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/WebhookRegistrationResponse'
        pagination:
          $ref: '#/components/schemas/WebhookListPagination'

    WebhookListPagination:
      type: object
      required:
        - limit
        - offset
        - returned
        - hasNextPage
      properties:
        limit:
          type: integer
          description: Maximum number of registrations requested.
          example: 50
        offset:
          type: integer
          description: Number of registrations skipped.
          example: 0
        returned:
          type: integer
          description: Number of registrations returned in this response.
          example: 25
        hasNextPage:
          type: boolean
          description: Whether there are more registrations beyond the current page.
          example: false

    WebhookEventTypesResponse:
      type: object
      required:
        - eventTypes
      properties:
        eventTypes:
          type: array
          description: The deterministic inventory of event types accepted in webhook registrations.
          items:
            type: string
          example:
            - payment_initiation.created
            - transfer.completed
            - transfer.rejected

    # Nested Schemas
    RecipientDetails:
      type: object
      required:
        - ispb
        - account
        - holderDocument
      properties:
        ispb:
          type: string
          pattern: '^[0-9]{8}$'
          description: The 8-digit ISPB code identifying the recipient's bank.
          example: '00000000'
        branch:
          type: string
          pattern: '^[0-9]{4}$'
          description: The 4-digit branch code (agência).
          example: '0001'
        account:
          type: string
          pattern: '^[0-9]{1,20}$'
          description: The account number, from 1 to 20 digits.
          example: '1234567890'
        accountType:
          $ref: '#/components/schemas/AccountType'
        holderName:
          type: string
          minLength: 1
          maxLength: 200
          description: The full name of the account holder.
          example: Maria Santos
        holderDocument:
          type: string
          pattern: '^[0-9]{11}$|^[0-9]{14}$'
          description: The holder's document number. CPF (11 digits) for individuals or CNPJ (14 digits) for companies.
          example: '12345678901'

    SenderDetails:
      type: object
      required:
        - accountId
        - holderName
      properties:
        accountId:
          type: string
          format: uuid
          nullable: true
          description: The Midaz account ID of the sender. Null for TED IN transfers from external banks.
          example: 019c96a0-0c0c-7221-8cf3-13313fb60081
        holderName:
          type: string
          description: The name of the account holder.
          example: João Silva

    StatusHistoryEntry:
      type: object
      required:
        - status
        - timestamp
      properties:
        previousStatus:
          allOf:
            - $ref: '#/components/schemas/TransferStatus'
          description: The status that the transfer left on this transition. Absent on the first transition.
        status:
          $ref: '#/components/schemas/TransferStatus'
        timestamp:
          type: string
          format: date-time
          example: '2026-02-01T18:30:00Z'
        reason:
          type: string
          nullable: true
          maxLength: 500
          description: The reason for the status change, if applicable.
          example: JD timeout after 3 retries

    PaginationInfo:
      type: object
      required:
        - limit
        - offset
        - returned
        - totalCount
        - hasNextPage
      properties:
        limit:
          type: integer
          description: Maximum number of items requested.
          example: 50
        offset:
          type: integer
          description: Number of items skipped.
          example: 0
        returned:
          type: integer
          description: Number of items returned in this response.
          example: 2
        totalCount:
          type: integer
          minimum: 0
          description: The total number of matching items.
          example: 123
        hasNextPage:
          type: boolean
          description: Whether there are more items beyond the current page.
          example: false

    ReconciliationGapsList:
      type: object
      required:
        - items
        - pagination
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/ReconciliationGap'
        pagination:
          $ref: '#/components/schemas/PaginationInfo'

    ReconciliationGap:
      type: object
      required:
        - id
        - latchedAt
        - transportSubreason
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier of the reconciliation-gap trace.
          example: 018f3a1c-1b2c-7def-8a9b-0c1d2e3f4a5b
        organizationId:
          type: string
          format: uuid
          description: Organization associated with the ambiguous inbound message, when known.
          example: 770e8400-e29b-41d4-a716-446655440003
        tenantId:
          type: string
          format: uuid
          description: Tenant whose database the gap was recorded in.
          example: 28b2302f-9c1d-4e5a-b6c7-8d9e0f1a2b3c
        transportSubreason:
          type: string
          description: The transport-level reason that left the outcome ambiguous.
          example: timeout_unknown_outcome
        lastSequenceCheckpoint:
          type: string
          description: The last processed sequence checkpoint at the time the gap was latched.
          example: SEQ-42
        latchedAt:
          type: string
          format: date-time
          description: Timestamp when the ambiguous outcome was latched for operator review.
          example: '2026-07-21T13:35:00Z'
        createdAt:
          type: string
          format: date-time
          description: Timestamp when the trace record was created.
          example: '2026-07-21T13:35:00Z'

    LedgerRoutingReadinessResponse:
      type: object
      properties:
        ready:
          type: boolean
          description: '`true` only when every line in `scopes` clears.'
          example: false
        documentState:
          type: string
          enum: [absent, sentinel, empty, bindings]
          example: bindings
        legacyPendingInitiations:
          type: integer
          description: Live initiations still pending from before ledger-scoped routing.
          example: 2
        readbackBudgetExhausted:
          type: boolean
          description: '`false` for every configuration that the contract accepts. `true` means a service defect; the scopes past the budget read `unavailable`.'
          example: false
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/LedgerRoutingReadinessScope'

    LedgerRoutingReadinessScope:
      type: object
      properties:
        organizationId:
          type: string
          format: uuid
          example: 11111111-1111-4111-8111-111111111111
        ledgerId:
          type: string
          description: Empty on an organization-level line; see `derivedFrom`.
          example: 22222222-2222-4222-8222-222222222222
        transferType:
          type: string
          enum: [P2P, TED_OUT, TED_IN, TED_IN_STR0005, TED_IN_STR0007, TED_IN_STR0029, TED_IN_STR0034, TED_IN_STR0037, TED_IN_STR0041, TED_IN_STR0051, CROSS_CORE_OUT, CROSS_CORE_IN, CROSS_CORE_RETURN_IN]
          example: TED_OUT
        transferCount:
          type: integer
          description: Transfers that this tuple has carried, whatever their outcome. It measures traffic, not settlement.
          example: 7
        lastTransferAt:
          type: string
          format: date-time
          example: '2026-09-16T12:30:00Z'
        derivedFrom:
          type: string
          enum: [ted_out_refund, ted_in_compensation, cross_core_return, ispb_onboarded_organization]
          description: The demand that produced this line. Absent once a binding answers the demand.
          example: ted_out_refund
        covered:
          type: boolean
          description: Whether a binding covers this line.
          example: true
        mode:
          type: string
          enum: [routes, omit]
          description: The binding mode. `omit` posts with the route fields left off.
          example: routes
        routesVerified:
          type: string
          enum: [ok, missing, mismatched, unavailable, not_applicable]
          description: Whether the accounting routes that the binding names exist inside this exact scope in Midaz.
          example: ok
        membership:
          type: string
          enum: [verified, unverified, not_applicable]
          example: verified

    TEDInPollerStatusResponse:
      type: object
      properties:
        enabled:
          type: boolean
          description: '`false` when JD polling is turned off.'
          example: true
        running:
          type: boolean
          example: false
        stale:
          type: boolean
          example: true
        staleAfterSec:
          type: integer
          example: 180
        reason:
          type: string
          description: The poller stop reason.
          example: receive_state_unknown
        lastCycleAt:
          type: string
          format: date-time
          example: '2026-09-06T04:54:00Z'
        lastSuccessAt:
          type: string
          format: date-time
          example: '2026-09-06T04:53:00Z'
        pendingReconciliationGaps:
          type: integer
          description: Always present, zero included.
          example: 1
        tenantId:
          type: string
          format: uuid
          description: Resolved tenant identifier. Omitted in single-tenant deployments.
          example: 11111111-1111-1111-1111-111111111111

    RetainedTEDInMessageList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/RetainedTEDInMessage'
        pagination:
          $ref: '#/components/schemas/PaginationInfo'

    RetainedTEDInMessage:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 018f3a1c-1b2c-7def-8a9b-0c1d2e3f4a5b
        messageSequence:
          type: string
          example: SEQ-42
        messageCode:
          type: string
          example: STR0008R2
        retentionReason:
          type: string
          enum:
            - UNRECOGNIZED_TYPE
            - ACCOUNT_NOT_OWNED
            - ACCOUNT_UNASSIGNED
            - RECIPIENT_NOT_FOUND_RETURN_UNAUTHORIZED
            - CLASSIFICATION_FAILED
            - AMBIGUOUS_CONTROL_ID
            - LEDGER_ROUTE_UNRESOLVED
            - RECIPIENT_AMBIGUOUS_ORGANIZATIONS
            - OWNERSHIP_DECISION_UNAVAILABLE
          example: UNRECOGNIZED_TYPE
        amount:
          type: string
          example: '15000.00'
        consignedCreditAmount:
          type: string
          description: On a `CLASSIFICATION_FAILED` STR0041R2, the consigned credit amount of the message (`VlrCredConsigd`). Absent on every other row, and when the message carries no readable value.
          example: '1500.00'
        bankTariff:
          type: string
          description: On a `CLASSIFICATION_FAILED` STR0041R2, the bank tariff of the message (`VlrTarBcoCredtd`). Absent on every other row, and when the message carries no readable value.
          example: '2.50'
        retainedAt:
          type: string
          format: date-time
          example: '2026-02-01T15:30:00Z'
        receivedAt:
          type: string
          format: date-time
          example: '2026-02-01T15:29:55Z'
        ageSeconds:
          type: integer
          description: Stops at `closedAt` once the row is closed.
          example: 3600
        contentBytes:
          type: integer
          description: How much of the message is held, in bytes.
          example: 2048
        drained:
          type: boolean
          example: true
        bindingsFingerprint:
          type: string
          nullable: true
          description: SHA-256 (hex) of the tenant's ledger routing bindings as they stood at the last refusal of an open `LEDGER_ROUTE_UNRESOLVED` hold. Two holds with different values were refused by different revisions of the bindings, and a value that stays the same across an edit means the edit has not been checked again yet. Null on every other row, and on a hold whose refusal read no bindings.
          example: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
        closedAt:
          type: string
          format: date-time
          description: '`closedAt`, `closedBy`, `outcome`, and `note` are present together or not at all, and they are absent on every row of the default `open` window.'
          example: '2026-02-03T09:12:00Z'
        closedBy:
          type: string
          example: acme/ana.souza
        outcome:
          type: string
          enum: [RETURNED, CREDITED, ABSORBED]
          example: RETURNED
        note:
          type: string
          example: funds wired back to the paying bank under control number 20260901000042

    UndeliverableTEDInCreditList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/UndeliverableTEDInCredit'
        pagination:
          $ref: '#/components/schemas/PaginationInfo'

    UndeliverableTEDInCredit:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 018f3a1c-1b2c-7def-8a9b-0c1d2e3f4a5b
        jdIncomingMessageId:
          type: string
          format: uuid
          example: 018f3a1c-1b2c-7def-8a9b-0c1d2e3f4a5c
        messageSequence:
          type: string
          example: SEQ-42
        controlNumber:
          type: string
          example: '202602010001'
        amount:
          type: string
          example: '15000.00'
        rejectionCategory:
          type: string
          enum:
            - RECIPIENT_NOT_FOUND
            - RECIPIENT_ACCOUNT_INVALID
            - MALFORMED_MESSAGE
            - FEE_RESOLUTION_FAILED
            - MIDAZ_REJECTION
            - CREDITED_AMOUNT_ZERO
            - ORGANIZATION_MISSING
            - ISPB_NOT_OWNED_BY_TENANT
          example: RECIPIENT_NOT_FOUND
        rejectionReason:
          type: string
          description: A fixed sentence derived from the category.
          example: no recipient account was found in any organization bound to this tenant
        resolutionStatus:
          type: string
          enum: [PENDING, IN_PROGRESS, RETAINED, RETURNED, ABSORBED, FAILED]
          example: PENDING
        reconciliationAttempts:
          type: integer
          example: 3
        lastVendorCode:
          type: string
          example: ALC02
        createdAt:
          type: string
          format: date-time
          example: '2026-02-01T15:30:00Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-02-01T15:35:00Z'

    CloseUnsettledTEDInCreditRequest:
      type: object
      required:
        - outcome
        - note
      properties:
        outcome:
          type: string
          description: '`RETURNED`, `CREDITED`, or `ABSORBED`. Accepted case-insensitively.'
          example: RETURNED
        note:
          type: string
          minLength: 10
          maxLength: 500
          description: The operator justification.
          example: funds wired back to the paying bank under control number 20260901000042; treasury ticket 8812

    CloseUnsettledTEDInCreditResponse:
      type: object
      properties:
        messageId:
          type: string
          format: uuid
          example: 770e8400-e29b-41d4-a716-446655440003
        outcome:
          type: string
          example: RETURNED
        note:
          type: string
          example: funds wired back to the paying bank under control number 20260901000042
        amount:
          type: string
          description: What this decision was worth, read from the retention when the message carried one, and from the devolution row otherwise. Omitted when no positive amount was parsed.
          example: '15000.00'
        closedAt:
          type: string
          format: date-time
          example: '2026-02-01T15:30:00Z'
        closedBy:
          type: string
          description: The stable subject from the bearer token, never a display name.
          example: acme/ana.souza
        retentionClosed:
          type: boolean
          example: true
        retentionReason:
          type: string
          example: RECIPIENT_NOT_FOUND_RETURN_UNAUTHORIZED
        retainedAt:
          type: string
          format: date-time
          example: '2026-02-01T15:29:55Z'
        devolutionClosed:
          type: boolean
          example: true
        devolutionPreviousStatus:
          type: string
          description: '`RETAINED` or `FAILED`: the devolution status before this call.'
          example: RETAINED
        devolutionStatus:
          type: string
          example: RETURNED

    ResolveTransferReconciliationRequest:
      type: object
      required:
        - action
        - reason
      properties:
        action:
          type: string
          enum: [SETTLE, REVERT, RE_POST, SETTLED_BY_HAND]
          example: SETTLE
        reason:
          type: string
          minLength: 10
          maxLength: 500
          example: JD cabine ticket 4471 confirms settlement at 10:42
        evidenceReference:
          type: string
          maxLength: 200
          description: Required for `SETTLED_BY_HAND`.
          example: STR0008 ctrl 202609061042
        clearingControlNumber:
          type: string
          maxLength: 20
          pattern: '^[A-Za-z0-9]{1,20}$'
          description: The STR control number read from the clearing house. Required only on the `SETTLE` of a voluntary return of a TED IN parked under `return_settlement_unknown` or `return_settlement_unknown_exhausted`.
          example: STR20261019000000001

    ResolveTransferReconciliationResponse:
      type: object
      properties:
        transferId:
          type: string
          format: uuid
          example: 770e8400-e29b-41d4-a716-446655440003
        status:
          type: string
          example: COMPLETED
        reconciliationStatus:
          type: string
          description: The reconciliation status after the resolution. On a voluntary return of a TED IN, it can also be `AWAITING` or `MANUAL_REVIEW`.
          example: RESOLVED
        resolvedAt:
          type: string
          format: date-time
          example: '2026-09-06T12:00:00Z'
        midazTransactionId:
          type: string
          format: uuid
          example: 11111111-2222-3333-4444-555555555555
        compensationTransactionId:
          type: string
          format: uuid
          example: 66666666-7777-4888-9999-aaaaaaaaaaaa
        previousCompensationTransactionId:
          type: string
          format: uuid
          example: 33333333-4444-4555-8666-777777777777

    RetryTransferReconciliationRequest:
      type: object
      required:
        - reason
      properties:
        reason:
          type: string
          maxLength: 500
          example: JD cabine confirms the settlement; requeueing for a fresh query

    RetryTransferReconciliationResponse:
      type: object
      properties:
        transferId:
          type: string
          format: uuid
          example: 770e8400-e29b-41d4-a716-446655440003
        reconciliationStatus:
          type: string
          example: AWAITING
        requeuedAt:
          type: string
          format: date-time
          example: '2026-08-28T15:04:05Z'

    RepairTransferLedgerScopeRequest:
      type: object
      required:
        - ledgerId
        - reason
      properties:
        ledgerId:
          type: string
          format: uuid
          example: 22222222-2222-4222-8222-222222222222
        reason:
          type: string
          minLength: 10
          maxLength: 500
          example: stranded row from the pre-routing backlog; CRM ticket 8812 names this ledger
        evidenceReference:
          type: string
          maxLength: 200
          example: CRM ticket 8812

    RepairTransferLedgerScopeResponse:
      type: object
      properties:
        transferId:
          type: string
          format: uuid
          example: 770e8400-e29b-41d4-a716-446655440003
        ledgerId:
          type: string
          format: uuid
          example: 22222222-2222-4222-8222-222222222222
        repairedAt:
          type: string
          format: date-time
          example: '2026-09-18T09:30:00Z'

    DashboardSummaryResponse:
      type: object
      properties:
        contractVersion:
          type: string
          description: Version of the dashboard response contract.
          example: v1
        window:
          type: string
          enum: [24h, 7d, 30d, all]
          description: The period that the counters cover.
          example: 24h
        windowStart:
          type: string
          format: date-time
          nullable: true
          description: The instant that the window opened. `null` for `all`.
          example: '2026-02-01T12:00:00Z'
        totalTransfers:
          type: integer
          description: Total number of transfers in the period.
          example: 128
        byStatus:
          type: array
          description: Transfer count grouped by status.
          items:
            type: object
            properties:
              status:
                type: string
                example: COMPLETED
              count:
                type: integer
                example: 101
        byType:
          type: array
          description: Transfer count grouped by type. Voluntary returns of a TED IN count under `TED_IN_RETURN`, not under `TED_OUT`. P2P returns count under no type. Cross-core TEDs sent count under `CROSS_CORE`, and the cross-core credits and returns received under `CROSS_CORE_IN`.
          items:
            type: object
            properties:
              type:
                type: string
                example: TED_OUT
              count:
                type: integer
                example: 97
        byReconciliationStatus:
          type: array
          nullable: true
          description: The current reconciliation backlog, grouped by status. The window does not apply. An empty array means that the backlog is empty. `null` means that the backlog could not be read, and `degraded` is then `true`.
          items:
            type: object
            properties:
              reconciliationStatus:
                type: string
                enum: [AWAITING, MANUAL_REVIEW]
                description: '`MANUAL_REVIEW` also counts the transfers stored as `EXHAUSTED`.'
                example: AWAITING
              count:
                type: integer
                example: 1
        health:
          type: object
          description: Operational health snapshot.
          properties:
            status:
              type: string
              enum: [UP, ATTENTION, DEGRADED]
              description: Overall health status. `UP` is healthy; `ATTENTION` flags an elevated failure rate; `DEGRADED` indicates the summary itself was computed in a degraded state.
              example: UP
            pendingCount:
              type: integer
              example: 0
            processingCount:
              type: integer
              example: 7
            terminalCount:
              type: integer
              example: 121
            failureCount:
              type: integer
              example: 20
            terminalFailRate:
              type: number
              format: float
              example: 0.1653
        generatedAt:
          type: string
          format: date-time
          description: Timestamp when the summary was generated.
          example: '2026-02-01T15:00:00Z'
        degraded:
          type: boolean
          description: Whether the system is in a degraded state.
          example: false
        degradationReasons:
          type: array
          description: List of degradation reasons when degraded is true.
          items:
            type: string

    # Enums
    TransferType:
      type: string
      enum:
        - TED_OUT
        - TED_IN
        - TED_IN_STR0005
        - TED_IN_STR0007
        - TED_IN_STR0029
        - TED_IN_STR0034
        - TED_IN_STR0037
        - TED_IN_STR0041
        - TED_IN_STR0051
        - P2P
        - CROSS_CORE
        - CROSS_CORE_IN
      description: |-
        The type of transfer. TED_OUT is an outgoing transfer to an external bank via JD SPB; the voluntary return of a TED IN is also a TED_OUT. TED_IN is an incoming transfer from an external bank. Each TED_IN_STR type is an incoming customer credit received as that STR message, for example TED_IN_STR0041 for an INSS payroll loan received as STR0041R2. P2P is a peer-to-peer transfer within the same institution (same ISPB). CROSS_CORE is a TED to an account that the other core of your institution holds, sent through the JD Courier. CROSS_CORE_IN is a cross-core credit, or the return of a cross-core TED, received from the other core.
      example: TED_OUT

    TransferStatus:
      type: string
      enum:
        - CREATED
        - PENDING
        - PROCESSING
        - COMPLETED
        - REJECTED
        - FAILED
        - CANCELLED
        - RECEIVED
      description: |-
        The current status of the transfer. For TED OUT: CREATED → PENDING → PROCESSING → COMPLETED, REJECTED, or FAILED. For P2P: CREATED → PROCESSING → COMPLETED or FAILED (PENDING is an SPB-specific state and does not apply to P2P). Transfers can be CANCELLED while in CREATED or PENDING status. For TED IN: RECEIVED → PROCESSING → COMPLETED or FAILED. A COMPLETED TED IN can also transition to FAILED in the event of a chargeback.
      example: COMPLETED

    InitiationStatus:
      type: string
      enum:
        - PENDING_CONFIRMATION
        - PROCESSED
        - EXPIRED
      description: |-
        The status of the initiation. PENDING_CONFIRMATION indicates the initiation is awaiting confirmation via the Process Transfer endpoint. PROCESSED indicates a transfer was created. EXPIRED indicates the initiation expired after 24 hours.
      example: PENDING_CONFIRMATION

    AccountType:
      type: string
      description: |-
        The account type following ISO 20022 codes. Known values include: CACC is a current account (Conta Corrente), SLRY is a salary account (Conta Salário), SVGS is a savings account (Conta Poupança), TRAN is a transactional account (Conta de Pagamento), and OTHR is any other account type.
      example: CACC

    # Error Schemas
    ProblemDocument:
      type: object
      description: The RFC 9457 problem document that the service answers every error with, on `application/problem+json`. Route-specific members (for example `refusalCode`, `transactionId`, `ledgerStatus`, `systemplaneKey`) appear flat at the top level beside these.
      additionalProperties: true
      required:
        - type
        - title
        - status
        - detail
        - code
        - requestId
        - service
        - category
      properties:
        type:
          type: string
          description: 'A URI that names the error code: `https://errors.lerian.studio/v1/<code>`.'
          example: https://errors.lerian.studio/v1/BTF-0200
        title:
          type: string
          description: The HTTP status text.
          example: Not Found
        status:
          type: integer
          description: The HTTP status code.
          example: 404
        detail:
          type: string
          description: A human-readable explanation of this occurrence.
          example: transfer not found
        instance:
          type: string
          description: The trace ID of this request, when one was recorded.
          example: 4bf92f3577b34da6a3ce929d0e0e4736
        errors:
          type: array
          description: Each rejected field. Present on field validation errors only.
          items:
            type: object
            properties:
              message:
                type: string
                description: Why the value was rejected.
                example: transferId must be a valid UUID
              location:
                type: string
                description: 'Where the value sits: `body.<field>` or `query.<field>`.'
                example: body.senderAccountId
        code:
          type: string
          description: The stable BTF error code.
          example: BTF-0200
        upstream:
          type: object
          description: The error that an upstream provider (JD SPB, Midaz) reported. Absent unless surfaced.
          properties:
            code:
              type: string
              description: The provider's own error code, verbatim.
              example: AAC90
            message:
              type: string
              description: The provider's own error message, bounded.
              example: Assinatura invalida
        requestId:
          type: string
          description: The request correlation ID. Present even when empty.
          example: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
        service:
          type: string
          description: The system that produced the error.
          example: plugin
        category:
          type: string
          description: The machine-readable error category.
          example: deterministic

  responses:
    # 400 Errors
    BadRequest:
      description: Indicates that the request contains invalid input.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            invalidInput:
              summary: Validation error
              value:
                type: https://errors.lerian.studio/v1/BTF-0001
                title: Bad Request
                status: 400
                detail: invalid request body
                code: BTF-0001
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    InvalidFilter:
      description: Indicates that one or more filter parameters are invalid.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            invalidDateRange:
              summary: Invalid date range
              value:
                type: https://errors.lerian.studio/v1/BTF-0001
                title: Bad Request
                status: 400
                detail: createdFrom must be less than or equal to createdTo
                code: BTF-0001
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    # 401 Errors
    Unauthorized:
      description: Indicates that the request is missing a valid authentication token.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            unauthorized:
              summary: Missing authorization
              value:
                type: https://errors.lerian.studio/v1/BTF-0401
                title: Unauthorized
                status: 401
                detail: Missing Token
                code: BTF-0401
                category: plugin
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    Forbidden:
      description: Indicates that the caller is authenticated but lacks permission for this operation.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            permissionDenied:
              summary: Caller lacks permission
              value:
                type: https://errors.lerian.studio/v1/BTF-0405
                title: Forbidden
                status: 403
                detail: the credential is not allowed to perform transfers:create on this request
                code: BTF-0405
                category: plugin
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    # 404 Errors
    AccountNotFound:
      description: |-
        BTF-0500: the sender account was not found in the CRM.

        BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            accountNotFound:
              summary: Invalid sender account
              value:
                type: https://errors.lerian.studio/v1/BTF-0500
                title: Not Found
                status: 404
                detail: sender account not found
                code: BTF-0500
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: crm

    TransferNotFound:
      description: |-
        BTF-0200: the transfer was not found or belongs to a different organization.

        BTF-0404: `X-Organization-Id` names an organization that this tenant does not operate.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            transferNotFound:
              summary: Transfer ID invalid
              value:
                type: https://errors.lerian.studio/v1/BTF-0200
                title: Not Found
                status: 404
                detail: transfer not found
                code: BTF-0200
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin
                transferId: 019c96a0-ab10-7cde-f1a2-0e1f2a3b4c5d

    # 410 Errors
    InitiationExpired:
      description: Indicates that the initiation has expired after 24 hours. A new initiation must be created.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            expired:
              summary: Initiation too old
              value:
                type: https://errors.lerian.studio/v1/BTF-0202
                title: Gone
                status: 410
                detail: initiation expired after 24 hours. please create a new initiation.
                code: BTF-0202
                category: deterministic
                expiresAt: '2026-01-31T15:30:00-03:00'
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    # 429 Errors
    RateLimitExceeded:
      description: Indicates that the rate limit has been exceeded. Retry after the number of seconds specified in the Retry-After header.
      headers:
        Retry-After:
          description: Seconds until rate limit resets
          schema:
            type: integer
            example: 60
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            rateLimitExceeded:
              summary: Too many requests
              value:
                type: https://errors.lerian.studio/v1/BTF-0429
                title: Too Many Requests
                status: 429
                detail: Rate limit exceeded
                code: BTF-0429
                category: rate_limit
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin
              description: |
                Rate-limit responses use the dedicated `BTF-0429` code with the `rate_limit` category. Clients should back off using the HTTP status `429` and the `Retry-After` header.

    # 5xx Errors
    ServiceUnavailable:
      description: Indicates that a dependency of this service is unavailable or is not configured in this deployment.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            repositoryUnavailable:
              summary: Repository unavailable
              value:
                type: https://errors.lerian.studio/v1/BTF-9008
                title: Service Unavailable
                status: 503
                detail: transfer repository unavailable
                code: BTF-9008
                category: transient
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin
            dlqInspectorUnavailable:
              summary: DLQ inspector not configured
              value:
                type: https://errors.lerian.studio/v1/BTF-9005
                title: Service Unavailable
                status: 503
                detail: DLQ inspector is not configured
                code: BTF-9005
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    InternalServerError:
      description: Indicates that an unexpected internal error occurred.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            internalError:
              summary: Internal error
              value:
                type: https://errors.lerian.studio/v1/BTF-9000
                title: Internal Server Error
                status: 500
                detail: Internal server error
                code: BTF-9000
                category: plugin
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    IdempotentInternalServerError:
      description: Indicates that an unexpected internal error occurred.
      headers:
        X-Idempotency-Fenced:
          $ref: '#/components/headers/XIdempotencyFenced'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            internalError:
              summary: Internal error
              value:
                type: https://errors.lerian.studio/v1/BTF-9000
                title: Internal Server Error
                status: 500
                detail: Internal server error
                code: BTF-9000
                category: plugin
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    IdempotentGatewayTimeout:
      description: Indicates that the operation did not complete before the request deadline.
      headers:
        X-Idempotency-Fenced:
          $ref: '#/components/headers/XIdempotencyFenced'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'

    # DLQ Errors
    DLQMessageNotFound:
      description: Indicates that the DLQ message was not found or belongs to a different tenant.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            notFound:
              summary: Unknown message id
              value:
                type: https://errors.lerian.studio/v1/BTF-0200
                title: Not Found
                status: 404
                detail: DLQ message not found
                code: BTF-0200
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    DLQMaxRetriesExceeded:
      description: Indicates that the DLQ message has exceeded the maximum allowed manual retry attempts.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            exceeded:
              summary: Manual retries exceeded
              value:
                type: https://errors.lerian.studio/v1/BTF-0305
                title: Conflict
                status: 409
                detail: DLQ message has exceeded maximum manual retry attempts
                code: BTF-0305
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    # Webhook Errors
    WebhookNotFound:
      description: Indicates that the webhook registration was not found or belongs to a different tenant.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            notFound:
              summary: Unknown webhook id
              value:
                type: https://errors.lerian.studio/v1/BTF-0306
                title: Not Found
                status: 404
                detail: webhook registration not found
                code: BTF-0306
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    WebhookConflict:
      description: Indicates that a webhook registration with the same endpoint already exists for this tenant.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            duplicate:
              summary: Endpoint already registered
              value:
                type: https://errors.lerian.studio/v1/BTF-0012
                title: Conflict
                status: 409
                detail: webhook registration already exists
                code: BTF-0012
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    WebhookValidationError:
      description: Indicates that the webhook payload violated a domain constraint, such as an invalid endpoint URL or an unsupported event type.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'
          examples:
            invalidEventType:
              summary: Unsupported event type
              value:
                type: https://errors.lerian.studio/v1/BTF-0002
                title: Unprocessable Entity
                status: 422
                detail: 'create webhook registration: webhook registration event type is unsupported: transfer.unknown'
                code: BTF-0002
                category: deterministic
                requestId: 6d3e2a68-1f2b-4c3d-9e4f-5a6b7c8d9e0f
                service: plugin

    IdempotencyKeyRefused:
      description: |-
        BTF-0014: an earlier request under this `X-Idempotency` key answered 5xx, so the key is fenced for the retention window. Read the resource and, only if the work did not land, send the identical request under a new key.

        BTF-0015: this service cannot read the idempotency record under this key. The remedy is the same.

        BTF-0017: this `X-Idempotency` key was already used for a different request. A repeat must be byte-identical, and a new request needs its own new key. BTF-0017 is decided before BTF-0014, so a fenced key resent with a changed body gets BTF-0017.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDocument'

  headers:
    RetryAfter:
      description: The number of seconds to wait before you send the request again.
      schema:
        type: integer
    XIdempotencyFenced:
      description: |-
        `true` when this 5xx fenced the `X-Idempotency` key, so a resend is refused with 422 BTF-0014. `false` when the fence write itself failed and only the in-flight lease holds the key, so a resend is refused with 409 BTF-0016 until that lease lapses. On a 503 BTF-0019, `false` means the handler succeeded and both its receipt and its fence were lost.

        The header is absent when nothing fenced the key. On a 500 raised by a panic, the protection of the key is unknown: read the resource and reconcile before you resend. On a 503 BTF-9000 answered while the idempotency store was down before the handler, nothing ran: send the same request again under the same key.
      schema:
        type: string
