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

# Optimize wallet strategy

> Returns a yield-maximizing allocation that satisfies the supplied constraints. The optimizer considers only sources whose `depositToken` matches the requested `token`. All request fields are optional; `token` defaults to `usdc`, so an empty body returns 100% to the highest-APY active USDC source. The token-keyed `allocations` object has the same shape as `strategy.allocations` on `POST /v2/wallets`, so callers can chain it directly into wallet creation.

When the best achievable allocation fails to satisfy `minBlendedApyBps`, the endpoint still returns `200` with the best-effort allocation and a top-level `unmetConstraints: ["minBlendedApyBps"]` field. Other infeasibility cases (no eligible sources, caps prevent full allocation) return `422`.



## OpenAPI

````yaml swagger/swagger-combined.yaml POST /v2/wallets/strategy/optimize
openapi: 3.0.0
info:
  title: Ground API
  description: Core API for portfolio wallets, deposits, withdrawals, and webhooks.
  version: 2.0.0
servers:
  - url: https://sandbox.groundtech.co
  - url: https://production.groundtech.co
security:
  - bearerAuth: []
tags:
  - name: System
    description: System health and utility endpoints.
  - name: Sandbox Faucets
    description: Sandbox-only test token faucets.
  - name: Portfolio Wallets
    description: >-
      Portfolio wallets with strategy allocation, deposits, withdrawals, and
      yield positions.
  - name: Webhook Endpoints
    description: Webhook endpoint management for portfolio wallet notifications.
paths:
  /v2/wallets/strategy/optimize:
    post:
      tags:
        - Portfolio Wallets
      summary: Optimize wallet strategy
      description: >-
        Returns a yield-maximizing allocation that satisfies the supplied
        constraints. The optimizer considers only sources whose `depositToken`
        matches the requested `token`. All request fields are optional; `token`
        defaults to `usdc`, so an empty body returns 100% to the highest-APY
        active USDC source. The token-keyed `allocations` object has the same
        shape as `strategy.allocations` on `POST /v2/wallets`, so callers can
        chain it directly into wallet creation.


        When the best achievable allocation fails to satisfy `minBlendedApyBps`,
        the endpoint still returns `200` with the best-effort allocation and a
        top-level `unmetConstraints: ["minBlendedApyBps"]` field. Other
        infeasibility cases (no eligible sources, caps prevent full allocation)
        return `422`.
      operationId: optimizePortfolioWalletStrategy
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                token:
                  type: string
                  enum:
                    - usdc
                    - usdt
                  default: usdc
                  description: >-
                    Stablecoin strategy lane to optimize. Only yield sources
                    with a matching `depositToken` are eligible.
                minBlendedApyBps:
                  type: integer
                  minimum: 0
                  maximum: 10000
                  description: >-
                    Minimum blended APY floor in basis points. If the best
                    achievable allocation falls below this floor, the response
                    still returns 200 and includes `unmetConstraints` with the
                    entry `minBlendedApyBps`.
                liquidity:
                  type: object
                  additionalProperties: false
                  properties:
                    maxSettlementHours:
                      type: integer
                      minimum: 0
                      description: >-
                        Maximum fixed elapsed settlement time in hours.
                        Calendar-based sources are excluded because banking days
                        are not fixed elapsed hours.
                concentration:
                  type: object
                  additionalProperties: false
                  properties:
                    maxPerSourcePct:
                      type: integer
                      minimum: 1
                      maximum: 100
                      description: Cap on any single source's allocation percentage.
                    maxPerProtocolPct:
                      type: integer
                      minimum: 1
                      maximum: 100
                      description: >-
                        Cap on the combined allocation percentage across all
                        sources sharing the same `protocol` slug. For example,
                        all Morpho vaults share the `morpho` protocol and count
                        against this cap together.
                allowedChains:
                  type: array
                  minItems: 1
                  items:
                    type: string
                  description: >-
                    Restrict eligible sources to the listed chains (e.g.,
                    `["ethereum", "base"]`). Omit to allow all chains.
                excludedSources:
                  type: array
                  items:
                    type: string
                  description: >-
                    Yield source IDs to exclude. Match by the kebab-case `id`
                    returned from `GET /v2/wallets/yield-sources`.
                excludedProtocols:
                  type: array
                  items:
                    type: string
                  description: Protocol slugs to exclude. Case-insensitive.
                maxSources:
                  type: integer
                  minimum: 1
                  description: >-
                    Cap on the number of distinct sources used in the
                    allocation.
            examples:
              empty:
                summary: No constraints — returns highest-APY USDC source
                value: {}
              usdt:
                summary: Optimize the USDT strategy lane
                value:
                  token: usdt
              diversified:
                summary: Force cross-protocol split
                value:
                  concentration:
                    maxPerProtocolPct: 50
              apy_floor:
                summary: Request a minimum blended APY
                value:
                  minBlendedApyBps: 500
                  concentration:
                    maxPerProtocolPct: 60
      responses:
        '200':
          description: Optimal (or best-effort) allocation
          content:
            application/json:
              schema:
                type: object
                required:
                  - allocations
                  - summary
                  - sources
                properties:
                  allocations:
                    $ref: '#/components/schemas/TokenStrategyAllocationsInput'
                    description: >-
                      Token-keyed allocation percentages summing to exactly 100
                      within the requested token lane. Shape matches the
                      `strategy.allocations` input on `POST /v2/wallets`.
                  summary:
                    type: object
                    required:
                      - blendedApyBps
                      - sourceCount
                      - protocolCount
                      - liquidityProfile
                    properties:
                      blendedApyBps:
                        type: integer
                        description: >-
                          Weighted-average APY of the returned allocation, in
                          basis points.
                      sourceCount:
                        type: integer
                        description: Number of distinct yield sources used.
                      protocolCount:
                        type: integer
                        description: Number of distinct protocols used.
                      liquidityProfile:
                        type: object
                        description: >-
                          Allocation percentages bucketed by structured
                          settlement policy (for example `atomic` or
                          `elapsed_seconds:86400`).
                        additionalProperties:
                          type: integer
                  sources:
                    type: array
                    description: Per-allocation detail for UI rendering.
                    items:
                      type: object
                      properties:
                        yieldSourceId:
                          $ref: '#/components/schemas/PortfolioWalletYieldSourceId'
                        pct:
                          type: number
                          multipleOf: 0.1
                          minimum: 0
                          maximum: 100
                        apyBps:
                          type: integer
                        protocol:
                          type: string
                        chain:
                          type: string
                        processingPolicy:
                          $ref: '#/components/schemas/YieldSourceProcessingPolicy'
                  unmetConstraints:
                    type: array
                    description: >-
                      Present only when the best-achievable allocation fails to
                      satisfy one or more non-hard constraints. Absent when all
                      constraints are met.
                    items:
                      type: string
                      enum:
                        - minBlendedApyBps
              examples:
                top_apy:
                  summary: Empty body — 100% highest-APY source
                  value:
                    allocations:
                      usdc:
                        - yieldSourceId: morpho-august-usdc-v2
                          pct: 100
                    summary:
                      blendedApyBps: 560
                      sourceCount: 1
                      protocolCount: 1
                      liquidityProfile:
                        atomic: 100
                    sources:
                      - yieldSourceId: morpho-august-usdc-v2
                        pct: 100
                        apyBps: 560
                        protocol: morpho
                        chain: ethereum
                        processingPolicy:
                          processingTimeBasis: elapsed_seconds
                          typicalMinUnits: 0
                          typicalMaxUnits: 0
                cross_protocol:
                  summary: maxPerProtocolPct=50 — cross-protocol split
                  value:
                    allocations:
                      usdc:
                        - yieldSourceId: morpho-august-usdc-v2
                          pct: 50
                        - yieldSourceId: syrup-usdc
                          pct: 50
                    summary:
                      blendedApyBps: 489
                      sourceCount: 2
                      protocolCount: 2
                      liquidityProfile:
                        atomic: 50
                        elapsed_seconds:86400: 50
                    sources:
                      - yieldSourceId: morpho-august-usdc-v2
                        pct: 50
                        apyBps: 560
                        protocol: morpho
                        chain: ethereum
                        processingPolicy:
                          processingTimeBasis: elapsed_seconds
                          typicalMinUnits: 0
                          typicalMaxUnits: 0
                      - yieldSourceId: syrup-usdc
                        pct: 50
                        apyBps: 418
                        protocol: maple
                        chain: ethereum
                        processingPolicy:
                          processingTimeBasis: elapsed_seconds
                          typicalMinUnits: 10800
                          typicalMaxUnits: 10800
                apy_floor_unmet:
                  summary: APY floor unreachable — best-effort with unmetConstraints
                  value:
                    allocations:
                      usdc:
                        - yieldSourceId: morpho-august-usdc-v2
                          pct: 100
                    summary:
                      blendedApyBps: 560
                      sourceCount: 1
                      protocolCount: 1
                      liquidityProfile:
                        atomic: 100
                    sources:
                      - yieldSourceId: morpho-august-usdc-v2
                        pct: 100
                        apyBps: 560
                        protocol: morpho
                        chain: ethereum
                        processingPolicy:
                          processingTimeBasis: elapsed_seconds
                          typicalMinUnits: 0
                          typicalMaxUnits: 0
                    unmetConstraints:
                      - minBlendedApyBps
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_field:
                  value:
                    error: minBlendedApyBps must be an integer in [0, 10000]
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Infeasibility — constraints cannot be satisfied.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - reason
                  - detail
                properties:
                  error:
                    type: string
                    enum:
                      - INFEASIBLE
                  reason:
                    type: string
                    enum:
                      - NO_ELIGIBLE_SOURCES
                      - CAPS_PREVENT_FULL_ALLOCATION
                  detail:
                    type: string
              examples:
                no_eligible_sources:
                  summary: Filters produced an empty set
                  value:
                    error: INFEASIBLE
                    reason: NO_ELIGIBLE_SOURCES
                    detail: No yield sources satisfy the supplied constraints.
                caps_prevent_full_allocation:
                  summary: Caps block a 100% allocation
                  value:
                    error: INFEASIBLE
                    reason: CAPS_PREVENT_FULL_ALLOCATION
                    detail: >-
                      Supplied concentration / maxSources caps cannot reach 100%
                      allocation with eligible sources.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    TokenStrategyAllocationsInput:
      type: object
      minProperties: 1
      additionalProperties: false
      properties:
        usdc:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/AllocationInput'
          description: USDC-compatible allocations. Percentages must sum to 100.
        usdt:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/AllocationInput'
          description: USDT-compatible allocations. Percentages must sum to 100.
      description: >-
        Token-keyed allocation groups. Each included token lane must
        independently sum to 100 percent.
    PortfolioWalletYieldSourceId:
      type: string
      description: >-
        Stable yield source ID from `GET /v2/wallets/yield-sources`, or the
        special `cash` id for cash held in the allocation's token lane. The live
        yield source catalog is environment-specific; fetch it before creating
        or updating a yield allocation.
    YieldSourceProcessingPolicy:
      type: object
      required:
        - processingTimeBasis
        - typicalMinUnits
        - typicalMaxUnits
      properties:
        processingTimeBasis:
          type: string
          enum:
            - elapsed_seconds
            - banking_days
        typicalMinUnits:
          type: integer
          minimum: 0
        typicalMaxUnits:
          type: integer
          minimum: 0
        processingEstimate:
          allOf:
            - $ref: '#/components/schemas/ProcessingEstimate'
          description: >-
            Preliminary completion estimate calculated when the catalog response
            is generated. Omitted for atomic actions and when maintained
            calendar coverage is unavailable.
    ErrorResponse:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: >-
            Human-readable error message. May change without notice; do not
            parse programmatically.
        code:
          type: string
          description: >-
            Machine-readable error code. Stable across API versions — safe to
            switch on in client code.
          enum:
            - validation_error
            - unknown_parameters
            - unsupported_chain
            - unsupported_token
            - invalid_destination_address
            - precision_overflow
            - invalid_cursor
            - unauthenticated
            - forbidden
            - wallet_not_found
            - wallet_limit_reached
            - withdrawal_not_found
            - deposit_not_found
            - yield_source_not_found
            - wallet_projection_unavailable
            - insufficient_funds
            - duplicate_request_id
            - request_id_conflict
            - invalid_position_weight
            - invalid_withdrawal_plan
            - payout_not_found
            - webhook_not_found
            - webhook_duplicate_url
            - withdrawal_not_cancellable
            - withdrawal_already_cancelled
            - withdrawal_payout_in_progress
            - withdrawal_policy_required
            - workflow_conflict
            - payout_already_terminal
            - payout_in_progress
            - payout_not_retryable
            - address_book_entry_not_found
            - address_book_duplicate_entry
            - address_book_whitelist_violation
            - rate_limited
            - rate_limit_exceeded
            - internal_error
            - wallet_creation_failed
            - service_temporarily_unavailable
    AllocationInput:
      type: object
      required:
        - yieldSourceId
        - pct
      additionalProperties: false
      properties:
        yieldSourceId:
          $ref: '#/components/schemas/PortfolioWalletYieldSourceId'
        pct:
          type: number
          multipleOf: 0.1
          minimum: 0
          maximum: 100
          description: >-
            Percentage of the token lane to allocate to this yield source or to
            cash when `yieldSourceId` is `cash`. Supports 0.1% increments.
    ProcessingEstimate:
      oneOf:
        - type: object
          required:
            - basis
            - typicalCompletionDate
            - typicalWindowEndDate
          properties:
            basis:
              type: string
              enum:
                - banking_days
            typicalCompletionDate:
              type: string
              format: date
            typicalWindowEndDate:
              type: string
              format: date
        - type: object
          required:
            - basis
            - typicalMinDuration
            - typicalMaxDuration
          properties:
            basis:
              type: string
              enum:
                - elapsed_seconds
            typicalMinDuration:
              type: string
              description: ISO-8601 duration for the lower end of the usual window.
            typicalMaxDuration:
              type: string
              description: ISO-8601 duration for the upper end of the usual window.
    AuthErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Authentication or authorization error message.
        message:
          type: string
          nullable: true
          description: Additional auth middleware detail, when present.
        code:
          type: string
          nullable: true
          enum:
            - unauthenticated
            - forbidden
          description: >-
            Some auth and scope failures include a stable code; middleware
            errors may omit it.
  responses:
    Unauthorized:
      description: >-
        The request is missing a valid bearer token, or the token is invalid or
        expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AuthErrorResponse'
          example:
            error: Unauthenticated request
            message: Missing or invalid bearer token
    TooManyRequests:
      description: >-
        Rate limit exceeded. Limits are enforced by API key/customer identity
        and by endpoint class. Retry with backoff; rate-limit headers are not
        currently emitted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Rate limit exceeded
            code: rate_limited
    InternalServerError:
      description: >-
        An unexpected error occurred while processing the request. The request
        can be safely retried with the same `requestId`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Internal server error
            code: internal_error
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````