openapi: 3.0.3
info:
  title: Avo Public API
  version: 1.0.0
  description: |
    ## Avo Public API

    Welcome to the Avo Public API documentation. This API provides programmatic access to portfolio management operations on Avo.

    {% hint style="info" %}
    **What can you do with this API?**
    Create managed portfolios, update token allocations, and manage your Avo portfolios programmatically.
    {% endhint %}

    **Base URL**
    `https://api.avo.so`

    ### Getting Started
    {% stepper %}
    {% step %}
    ### Obtain API credentials
    The API is currently available publicly however under controlled access to ensure early immediate support for select deployers. Once the API becomes Open Access, it will adopt a soft staking model where deployers need to soft stake (hold 100,000 AVO in a wallet) to keep their API key active. To obtain a key, reach out to Avo through [Discord](https://discord.gg/avodotso) and open a ticket in the tickets channel.
    {% endstep %}
    {% step %}
    ### Add authentication header
    Include your token in requests: `Authorization: Bearer <your-jwt-token>`
    {% endstep %}
    {% step %}
    ### Make your first request
    Call `GET /health` to verify connectivity, then create your first portfolio.
    {% endstep %}
    {% endstepper %}

    ### Authentication

    All endpoints under `/api/*` require JWT authentication. Include the JWT token in the Authorization header:

    ```
    Authorization: Bearer <your-jwt-token>
    ```

    {% hint style="warning" %}
    **Public endpoints** (`/health`, `/ping`) do not require authentication.
    {% endhint %}

    ### Downloads

    - [Postman Collection](https://gist.githubusercontent.com/0xpaperhead/b54f060053f691e60e6c5ebd4a9cb546/raw/postman-collection.json)

servers:
  - url: https://api.avo.so

tags:
  - name: health
    x-page-title: Health
    x-page-description: Health check endpoints for monitoring service availability
    x-page-icon: heart-pulse
  - name: portfolio
    x-page-title: Portfolio
    x-page-description: Create and manage portfolios
    x-page-icon: briefcase

paths:
  /health:
    get:
      summary: Public health check
      description: Returns the health status of the API. No authentication required.
      tags: [health]
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl https://api.avo.so/health
        - lang: JavaScript
          label: Fetch
          source: |
            fetch('https://api.avo.so/health')
              .then(r => r.json())
              .then(console.log)

  /ping:
    get:
      summary: Public ping endpoint
      description: Simple ping endpoint to check if the service is reachable. No authentication required.
      tags: [health]
      operationId: ping
      responses:
        "200":
          description: Pong response
          content:
            text/plain:
              schema:
                type: string
                example: pong
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl https://api.avo.so/ping

  /api/health:
    get:
      summary: Authenticated health check
      description: Returns detailed health status of the API gateway. Requires authentication.
      tags: [health]
      operationId: getAuthenticatedHealth
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticatedHealthResponse"
        "401":
          description: Authentication required or invalid token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimpleError"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl https://api.avo.so/api/health \
              -H "Authorization: Bearer YOUR_JWT_TOKEN"

  /api/portfolio/ping:
    get:
      summary: Portfolio service health check
      description: Simple ping to the portfolio service to verify connectivity.
      tags: [portfolio]
      operationId: pingPortfolioService
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Portfolio service is reachable
          content:
            text/plain:
              schema:
                type: string
                example: pong
        "401":
          description: Authentication required or invalid token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimpleError"
        "502":
          description: Unable to reach upstream service
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimpleError"

  /api/portfolio/create:
    post:
      summary: Create a portfolio
      description: |
        Creates a new portfolio. Uses `multipart/form-data` to support optional image upload.

        This is the first step towards creating a portfolio on Avo. This endpoint creates a portfolio with a DRAFT status (not live or visible to users) and an empty positions array. Target positions should be added later via the Update Portfolio endpoint.

        ### Portfolio Types

        > **Note:** The type field is currently for aesthetic purposes only. Use `PORTFOLIO` as the default option. However, if you're deciding between a standard portfolio and an index, we recommend building an `INDEX_PORTFOLIO` as it currently has better visibility on Avo.

        | Type | Description |
        |------|-------------|
        | `PORTFOLIO` | A standard portfolio (default) |
        | `INDEX_PORTFOLIO` | A portfolio that tracks a basket of tokens with dynamic rebalancing (better visibility on Avo) |
      tags: [portfolio]
      operationId: createManagedPortfolio
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/CreateManagedPortfolioRequest"
      responses:
        "200":
          description: Portfolio created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateManagedPortfolioResponse"
        "400":
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Authentication required or invalid token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimpleError"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
      x-codeSamples:
        - lang: cURL
          label: Basic
          source: |
            curl -X POST https://api.avo.so/api/portfolio/create \
              -H "Authorization: Bearer YOUR_JWT_TOKEN" \
              -F "name=My Index Portfolio" \
              -F "type=INDEX_PORTFOLIO"
        - lang: cURL
          label: With Image
          source: |
            curl -X POST https://api.avo.so/api/portfolio/create \
              -H "Authorization: Bearer YOUR_JWT_TOKEN" \
              -F "name=My Portfolio" \
              -F "type=INDEX_PORTFOLIO" \
              -F "description=A curated index of top tokens" \
              -F "image=@/path/to/image.jpg"
        - lang: JavaScript
          label: Fetch
          source: |
            const formData = new FormData();
            formData.append('name', 'My Index Portfolio');
            formData.append('type', 'INDEX_PORTFOLIO');
            formData.append('description', 'A curated index of top tokens');

            fetch('https://api.avo.so/api/portfolio/create', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer YOUR_JWT_TOKEN'
              },
              body: formData
            })
              .then(r => r.json())
              .then(console.log)

  /api/portfolio/update:
    post:
      summary: Update portfolio target positions
      description: |
        Updates the target positions for an existing portfolio. Target positions define the desired allocation of tokens in the portfolio.

        ### Allocation Rules

        - Each `allocation` value must be between `0` and `1` (representing 0% to 100%)
        - The sum of all allocations should equal `1` (100%)
        - You can include 1-100 target positions (recommended to keep it under 30 positions for better UX)

        ### Common Token Mints

        | Token | Mint Address |
        |-------|--------------|
        | SOL (Wrapped) | `So11111111111111111111111111111111111111112` |
        | AVO | `GdZ9rwHyKcriLdbSzhtEFLe5MLs7Vk6AY1aE5ei7nsmP` |
        | INFRA | `D1wZHkfk8d6QsCjF3NTiYHLzsZJ2Qb4Q7WBFFbGuzBLV` |
      tags: [portfolio]
      operationId: updatePortfolio
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePortfolioRequest"
      responses:
        "200":
          description: Portfolio updated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdatePortfolioResponse"
        "400":
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Authentication required or invalid token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimpleError"
        "404":
          description: Portfolio not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
      x-codeSamples:
        - lang: cURL
          label: CLI
          source: |
            curl -X POST https://api.avo.so/api/portfolio/update \
              -H "Authorization: Bearer YOUR_JWT_TOKEN" \
              -H "Content-Type: application/json" \
              -d '{
                "portfolio_id": "550e8400-e29b-41d4-a716-446655440000",
                "target_positions": [
                  {"mint": "So11111111111111111111111111111111111111112", "allocation": 0.5},
                  {"mint": "GdZ9rwHyKcriLdbSzhtEFLe5MLs7Vk6AY1aE5ei7nsmP", "allocation": 0.3},
                  {"mint": "D1wZHkfk8d6QsCjF3NTiYHLzsZJ2Qb4Q7WBFFbGuzBLV", "allocation": 0.2}
                ]
              }'
        - lang: JavaScript
          label: Fetch
          source: |
            fetch('https://api.avo.so/api/portfolio/update', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer YOUR_JWT_TOKEN',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                portfolio_id: '550e8400-e29b-41d4-a716-446655440000',
                target_positions: [
                  { mint: 'So11111111111111111111111111111111111111112', allocation: 0.5 },
                  { mint: 'GdZ9rwHyKcriLdbSzhtEFLe5MLs7Vk6AY1aE5ei7nsmP', allocation: 0.3 },
                  { mint: 'D1wZHkfk8d6QsCjF3NTiYHLzsZJ2Qb4Q7WBFFbGuzBLV', allocation: 0.2 }
                ]
              })
            })
              .then(r => r.json())
              .then(console.log)

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token for authentication

  schemas:
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          example: healthy
        timestamp:
          type: string
          format: date-time
          example: "2025-12-27T07:51:02.617Z"
        uptime:
          type: number
          format: double
          description: Server uptime in seconds
          example: 91.807410068

    AuthenticatedHealthResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: Health check successful!
        uptime:
          type: string
          example: "3600 seconds"
        timestamp:
          type: string
          format: date-time
          example: "2025-01-15T10:30:00.000Z"

    CreateManagedPortfolioRequest:
      type: object
      required: [name, type]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 30
          description: Name for the managed portfolio (required, 1-30 characters)
          example: My Index Portfolio
        type:
          type: string
          enum: [PORTFOLIO, INDEX_PORTFOLIO]
          description: Type of portfolio to create
          example: PORTFOLIO
          x-enumDescriptions:
            PORTFOLIO: A standard portfolio (default)
            INDEX_PORTFOLIO: A portfolio that tracks a basket of tokens with dynamic rebalancing (better visibility on Avo)
        beneficiary_wallet:
          type: string
          minLength: 32
          maxLength: 44
          description: The wallet that will receive deployer fees. Must have some initial SOL (minimum rent exemption amount required to receive SOL).
          example: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
        description:
          type: string
          minLength: 1
          maxLength: 500
          description: Optional description for the portfolio (max 500 characters)
          example: A curated index of top tokens
        image:
          type: string
          format: binary
          description: Optional image file (JPEG or PNG, max 10MB)

    CreateManagedPortfolioResponse:
      type: object
      required: [portfolio_id]
      properties:
        portfolio_id:
          type: string
          description: The unique identifier of the created portfolio
          example: 550e8400-e29b-41d4-a716-446655440000

    UpdatePortfolioRequest:
      type: object
      required: [portfolio_id, target_positions]
      properties:
        portfolio_id:
          type: string
          minLength: 1
          description: The unique identifier of the portfolio to update
          example: 550e8400-e29b-41d4-a716-446655440000
        target_positions:
          type: array
          minItems: 1
          maxItems: 100
          description: Array of target position allocations
          items:
            $ref: "#/components/schemas/TargetPosition"

    TargetPosition:
      type: object
      required: [mint, allocation]
      properties:
        mint:
          type: string
          minLength: 1
          description: Token mint address (Solana SPL token address)
          example: So11111111111111111111111111111111111111112
        allocation:
          type: number
          format: double
          minimum: 0
          maximum: 1
          description: Allocation percentage as a decimal (0-1, where 1 = 100%)
          example: 0.25

    UpdatePortfolioResponse:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          description: Whether the update was successful
          example: true
        message:
          type: string
          description: Success or status message
          example: Portfolio target positions updated successfully

    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          required: [source, code, message, function]
          properties:
            source:
              type: string
              description: Service that generated the error
              example: portfolio-service
            code:
              type: string
              description: Error code
              example: VALIDATION_ERROR
            message:
              type: string
              description: Human-readable error message
              example: Invalid portfolio ID
            function:
              type: string
              description: Function where the error occurred
              example: handleUpdatePortfolio

    SimpleError:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: Invalid auth token
