> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pods.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Unlock foreign currency capability

> After Avenia approves a foreign KYC profile, submit the currency unlock request for USD. Pods forwards the unlock to Avenia, refreshes account metadata, and returns the normalized KYC status including usdEnabled. Idempotent when the capability is already enabled.



## OpenAPI

````yaml /openapi.yaml post /api/v1/kyc/currency-unlock
openapi: 3.0.0
info:
  version: 2.0.0
  title: Pods API
  description: >
    # Pods API Documentation


    Pods API is a comprehensive DeFi aggregation platform that enables
    cross-chain and same-chain transactions for yield strategies.


    ## Key Features


    - **Cross-Chain Swaps**: Swap tokens across different blockchain networks

    - **Same-Chain Swaps**: Optimal routing for swaps within the same network

    - **Yield Strategies**: Access to multiple DeFi protocols (Aave, Morpho,
    Lido, etc.)

    - **Wallet Tracking**: Monitor positions and yields across protocols

    - **Transaction Monitoring**: Real-time status updates for cross-chain
    transactions


    ## Authentication


    Most API endpoints require authentication using an API key in the
    `x-api-key` header:


    ```

    x-api-key: your-api-key-here

    ```


    Obtain your API key from the Pods dashboard.


    **Exceptions:**

    - `GET /health` is public and does not require authentication.

    - Customer dashboard endpoints (e.g. `GET /customers/me`,
    `/customers/:id/tokens`, `/customers/:id/token-groups`) require a **Bearer**
    token (Supabase or Magic JWT). API key alone is not sufficient for these
    routes because they enforce user role checks.


    ## Base URL


    Production: `https://api.pods.finance`


    ## Rate Limits


    - 100 requests per minute per API key

    - 1000 requests per hour per API key


    ## Quote Expiration


    Quotes expire after 5 minutes. The `deadline` field is a Unix timestamp in
    seconds and `deadlineDate` is an ISO 8601 string (UTC); request a new quote
    when the deadline has passed.


    ## Error Handling


    All errors follow a consistent format. The HTTP status line is the source of
    truth — the body does **not** include `httpStatus`:


    ```json

    {
      "error": {
        "code": "ERROR_CODE",
        "message": "Human-readable error message",
        "details": {}
      }
    }

    ```


    ## Common Error Codes


    - **QUOTE_NOT_FOUND** (404): Quote not found or expired

    - **ACTION_NOT_FOUND** (404): Action not found

    - **STRATEGY_NOT_FOUND** (404): Strategy not found

    - **RECURRING_COLLECTION_REQUIRES_FEE** (400): Recurring collection requires
    a performance or markup fee configured

    - **RECURRING_MARKUP_RECIPIENT_REQUIRED** (422): Live recurring collection
    with a markup fee requires a non-zero markup fee recipient

    - **RECURRING_RECIPIENT_ROTATION_PENDING_DISPATCH** (409): Markup fee
    recipient cannot be rotated while a recurring collection transaction is
    unresolved

    - **RECURRING_REGISTRY_CONTROLLER_NOT_CONFIGURED** (503): Recurring
    collection controller is not configured for this network

    - **RECURRING_REGISTRY_MISMATCH** (409): On-chain recurring collection
    registry is not synchronized

    - **INVALID_ACTION_TYPE** (400): Action type is not supported for this
    endpoint

    - **QUOTE_EXPIRED** (400): Quote has expired

    - **QUOTE_VALIDATION_ERROR** (400): Quote validation failed

    - **PIX_THIRD_PARTY_QUOTE_MISMATCH** (400): thirdParty must match the Pix
    offramp quote

    - **MISSING_PIX_KEY** (400): pixKey is required to create the BRL Pix payout
    ticket

    - **INVALID_PIX_BRCODE_FORMAT** (400): pixKey looks like a Pix BR Code but
    failed format/checksum validation

    - **PIX_BRCODE_EXPIRED** (400): This Pix BR Code has expired

    - **PIX_BRCODE_LOOKUP_FAILED** (502): Failed to look up Pix BR Code details
    from Avenia

    - **PIX_BRCODE_REQUOTE_REQUIRED** (400): This Pix BR Code has a fixed amount
    that was not priced into the existing quote; request a new quote

    - **PROVIDER_NOT_FOUND** (404): Swap provider not found

    - **NO_ROUTE_FOUND** (400): No swap route available between specified chains

    - **INVALID_PREFERRED_PROVIDER** (400): Unknown preferredProvider name

    - **PREFERRED_PROVIDER_NOT_ELIGIBLE** (400): preferredProvider is not
    eligible for this swap route

    - **PREFERRED_PROVIDER_QUOTE_UNAVAILABLE** (400): No quote was returned for
    the requested preferredProvider

    - **QUOTE_GENERATION_FAILED** (400): Could not generate quote for specified
    parameters

    - **FEE_SPONSORSHIP_NOT_ALLOWED** (403): Fee sponsorship is not allowed for
    this customer

    - **CUSTOMER_STRATEGY_NOT_FOUND** (404): Customer strategy not found

    - **FEE_ACTIVATION_IN_PROGRESS** (409): Profit fee activation is already
    reconciling

    - **FEE_ACTIVATION_TASK_NOT_PERSISTED** (500): Fee activation task could not
    be persisted

    - **INVALID_TX_HASH** (400): Invalid transaction hash format

    - **TX_UPDATE_FAILED** (500): Failed to update transaction status

    - **INVALID_AMOUNT_PARAMS** (400): Cannot specify both amountIn and
    amountOut simultaneously

    - **MISSING_AMOUNT_PARAMS** (400): Either amountIn or amountOut is required

    - **INVALID_SLIPPAGE** (400): slippage must be a number between 0.0001 and
    0.5 (fraction of 1, e.g. 0.01 = 1%)

    - **AMOUNT_IN_TOO_LOW** (400): AmountIn is below the minimum for this route

    - **AMOUNT_IN_TOO_HIGH** (400): AmountIn exceeds available liquidity for
    this route. Try a smaller amount.

    - **AMOUNT_IN_TOO_LOW_OFFRAMP** (400): AmountIn must be greater than 1 USDC

    - **AMOUNT_IN_TOO_HIGH_OFFRAMP** (400): AmountIn must be less than 5k USDC

    - **AMOUNT_OUT_TOO_LOW_OFFRAMP** (400): AmountOut is below the route minimum
    (0.5 BRL on Pix BRLA/BRS, 5 BRLA on USDC Pix)

    - **AMOUNT_OUT_TOO_HIGH_OFFRAMP** (400): AmountOut must be less than 26k
    BRLA

    - **AMOUNT_EXCEEDS_PROVIDER_LIMIT** (409): Requested amount exceeds the
    remaining provider withdrawal limit

    - **AMOUNT_IN_TOO_LOW_ONRAMP** (400): AmountIn is below the route minimum
    (0.5 BRL on Pix BRLA/BRS, 1 BRLA on USDC Pix)

    - **AMOUNT_IN_TOO_HIGH_ONRAMP** (400): AmountIn must be less than 75k BRLA

    - **AMOUNT_OUT_TOO_LOW_ONRAMP** (400): AmountOut must be greater than 1 USDC

    - **AMOUNT_OUT_TOO_HIGH_ONRAMP** (400): AmountOut must be less than 15k USDC

    - **TRANSFER_VALIDATION_FAILED** (400): Token transfer validation failed

    - **REFUND_PROCESSING_FAILED** (500): Failed to process refund transaction

    - **INSUFFICIENT_AMOUNT_FOR_REFUND** (400): Transferred amount insufficient
    to cover refund fee

    - **FORBIDDEN** (403): Access denied

    - **INTERNAL_SERVER_ERROR** (500): An unexpected error occurred

    - **BYTECODE_GENERATION_FAILED** (500): Failed to generate transaction
    bytecode

    - **SWAP_EXECUTION_FAILED** (500): Failed to execute swap transaction

    - **KAMINO_SERVICE_UNAVAILABLE** (503): Lending service is temporarily
    unavailable. Please try again later.

    - **KAMINO_BAD_REQUEST** (400): Invalid request to lending service

    - **DEPOSIT_NOT_FOUND** (404): Bitcoin deposit not found

    - **SYMBIOSIS_API_ERROR** (502): Error communicating with Symbiosis API


    ## Support


    - Documentation: https://docs.pods.finance

    - Support: support@pods.finance
  contact:
    name: Pods Support
    email: support@pods.finance
    url: https://pods.finance
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
  - url: https://api.pods.finance
    description: Production server
security: []
tags:
  - name: Health
    description: Health check endpoints
  - name: KYC
    description: >-
      KYC onboarding and status endpoints: Brazilian and foreign Sumsub import,
      BigDataCorp capture, external evidence, and USD currency unlock
  - name: Swap
    description: Token swap operations
  - name: Tracking
    description: Execution tracking for swaps, transfers, and strategy operations
  - name: Strategies
    description: DeFi yield strategy operations
  - name: Ondo
    description: Ondo Global Markets stock status and tradability
  - name: Tokens
    description: Token catalog and metadata
  - name: Transfer
    description: Token transfer transaction generation
  - name: Wallets
    description: Wallet position tracking
  - name: Customers
    description: Customer account management (Bearer auth)
  - name: Quotes
    description: Quote history and management
  - name: Smart Account
    description: ERC-4337 Gnosis Safe smart account provisioning
paths:
  /api/v1/kyc/currency-unlock:
    post:
      tags:
        - KYC
      summary: Unlock foreign currency capability
      description: >-
        After Avenia approves a foreign KYC profile, submit the currency unlock
        request for USD. Pods forwards the unlock to Avenia, refreshes account
        metadata, and returns the normalized KYC status including usdEnabled.
        Idempotent when the capability is already enabled.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitCurrencyKycUnlockRequest'
      responses:
        '200':
          description: Currency unlock submitted or already enabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycStatusResponse'
        '401':
          description: Unauthorized - missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: KYC profile not approved for this currency
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: KYC profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Avenia currency unlock failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    SubmitCurrencyKycUnlockRequest:
      type: object
      properties:
        currency:
          type: string
          enum:
            - usd
          description: >-
            Foreign currency capability to unlock after Avenia approves the KYC
            profile
          example: usd
        kycUserId:
          type: string
          format: uuid
          description: >-
            Pods-generated KYC user id returned by the foreign share-token
            endpoint
          example: 550e8400-e29b-41d4-a716-446655440000
      required:
        - currency
        - kycUserId
      description: Request Avenia currency unlock for an approved foreign KYC profile
    KycStatusResponse:
      type: object
      properties:
        kycUserId:
          type: string
          format: uuid
          description: Pods-generated public KYC user id
          example: 550e8400-e29b-41d4-a716-446655440000
        provider:
          $ref: '#/components/schemas/KycProvider'
        email:
          type: string
          format: email
          example: user@example.com
        walletAddress:
          $ref: '#/components/schemas/WalletAddress'
        status:
          $ref: '#/components/schemas/KycProfileStatus'
        brlaEnabled:
          type: boolean
          example: false
        usdEnabled:
          type: boolean
          description: True when the profile is approved for USD WIRE/ACH ramps via Avenia
          example: false
        eurEnabled:
          type: boolean
          description: Reserved for a future EUR capability; always false today
          example: false
        rejectReason:
          type: string
          nullable: true
        providerStatus:
          type: string
          nullable: true
          example: COMPLETED
        providerResult:
          type: string
          nullable: true
          description: >-
            Provider-specific normalized result. BigDataCorp may return values
            such as WAITING_ADDRESS_TO_SUBMIT_TO_AVENIA, DUPLICATE_TAX_ID, or
            AVENIA_KYC_SUBMIT_FAILED before the final Pods status is approved.
          example: APPROVED
        providerMessage:
          type: string
          nullable: true
          description: >-
            Provider message persisted for support and retry decisions, when
            available.
        livenessStatus:
          type: string
          nullable: true
          example: passed
        documentStatus:
          type: string
          nullable: true
          example: passed
        birthDate:
          type: string
          nullable: true
          description: >-
            Resolved birth date in YYYY-MM-DD format for BigDataCorp profiles.
            Null while pending, unavailable, or not applicable to the provider.
          example: '1990-01-01T00:00:00.000Z'
        ageAtVerification:
          type: number
          nullable: true
          example: 36
        bigDataCorpIframeCreatedAt:
          type: string
          nullable: true
          format: date-time
          description: When the current BigDataCorp iframe session was created.
          example: '2026-06-15T12:00:00.000Z'
        bigDataCorpCompletedAt:
          type: string
          nullable: true
          format: date-time
          description: >-
            When BigDataCorp completed document, liveness, and identity
            verification.
          example: '2026-06-15T12:04:00.000Z'
        aveniaSubmissionStartedAt:
          type: string
          nullable: true
          format: date-time
          description: >-
            When Pods started the final Avenia submission. Repeated submit
            requests return the existing provider_pending result while this
            operation is in progress.
          example: '2026-06-15T12:04:30.000Z'
        aveniaSubmittedAt:
          type: string
          nullable: true
          format: date-time
          description: When the verified BigDataCorp profile was submitted to Avenia.
          example: '2026-06-15T12:05:00.000Z'
        aveniaApprovedAt:
          type: string
          nullable: true
          format: date-time
          description: >-
            When Avenia approved the KYC attempt. For Brazil profiles this
            coincides with brlaEnabled; for foreign profiles poll usdEnabled
            after currency unlock.
          example: '2026-06-15T12:06:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-06-15T12:01:00.000Z'
        documentEvidenceSource:
          allOf:
            - $ref: '#/components/schemas/KycEvidenceSource'
            - nullable: true
              description: >-
                Who captured the document. Null for profiles created before
                evidence sources were recorded.
        livenessEvidenceSource:
          allOf:
            - $ref: '#/components/schemas/KycEvidenceSource'
            - nullable: true
              description: Who ran the liveness check.
        documentType:
          type: string
          nullable: true
          description: Type of identity document submitted, when known.
        reason:
          allOf:
            - $ref: '#/components/schemas/KycRejectionReason'
            - nullable: true
              description: >-
                Present only when status is rejected or rejected_retryable.
                Prefer this over rejectReason and providerResult, which carry
                raw provider values and vary between providers.
      required:
        - kycUserId
        - provider
        - email
        - walletAddress
        - status
        - brlaEnabled
        - usdEnabled
        - eurEnabled
        - rejectReason
        - providerStatus
        - providerResult
        - providerMessage
        - livenessStatus
        - documentStatus
        - birthDate
        - ageAtVerification
        - bigDataCorpIframeCreatedAt
        - bigDataCorpCompletedAt
        - aveniaSubmissionStartedAt
        - aveniaSubmittedAt
        - aveniaApprovedAt
        - documentEvidenceSource
        - livenessEvidenceSource
        - documentType
        - reason
      description: Current normalized KYC profile status
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              nullable: true
          required:
            - code
            - message
      required:
        - error
      example:
        error:
          code: QUOTE_NOT_FOUND
          message: Quote not found or expired
    KycProvider:
      type: string
      enum:
        - sumsub
        - bigdatacorp
        - external_evidence
      description: >-
        KYC provider used for the profile. external_evidence means the capture
        was performed by the customer and submitted to Pods as attested
        evidence.
      example: sumsub
    WalletAddress:
      type: string
      description: >-
        Wallet address stored on the Ramp KYC profile. Accepts a checksummed EVM
        address or a Solana public key. Later Pix quotes must use the same
        address as destinationAddress (onramp) or originAddress (offramp).
      example: EXYhCNamLPFfSMekSn7Lzc2cP32aZpRa7ok5gErdJzcj
    KycProfileStatus:
      type: string
      enum:
        - created
        - provider_pending
        - approved
        - rejected
        - rejected_retryable
        - blocked
      description: Normalized Pods KYC status
      example: provider_pending
    KycEvidenceSource:
      type: string
      enum:
        - customer_attested
        - deframe_managed
      description: >-
        Who performed the capture. customer_attested means the customer ran the
        document or liveness check and attested to its result; deframe_managed
        means Pods ran it.
      example: customer_attested
    KycRejectionReason:
      type: object
      properties:
        code:
          type: string
          description: >-
            Stable Pods rejection code. Program against this value: it does not
            change when a provider is added or replaced. Known codes are
            LIVENESS_FACE_MISMATCH, LIVENESS_FRAUD_SUSPECTED,
            LIVENESS_QUALITY_INSUFFICIENT, IMAGE_QUALITY_INSUFFICIENT,
            DOCUMENT_INVALID, IDENTITY_DATA_MISMATCH, IDENTITY_CPF_MISMATCH,
            TAX_ID_INVALID, TAX_ID_ALREADY_VERIFIED, IDENTITY_DECEASED,
            COMPLIANCE_BLOCKED, UNDER_18 and PROVIDER_ERROR.
          example: TAX_ID_ALREADY_VERIFIED
        message:
          type: string
          description: Human-readable explanation. Safe to show to the applicant.
          example: This person is already verified under another record.
        retryable:
          type: boolean
          description: >-
            Whether resubmitting can produce a different outcome. False means
            the rejection is terminal for this person.
          example: false
        retryFrom:
          type: string
          enum:
            - liveness
            - documents
            - applicant
          description: >-
            Which step to send the applicant back to. Present only when
            retryable is true.
          example: liveness
      required:
        - code
        - message
        - retryable
      description: >-
        Normalized rejection. The raw provider label is deliberately excluded —
        it belongs in logs, not in a contract.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: API key for authentication. Obtain from your Pods dashboard.

````