{
  "openapi": "3.0.3",
  "info": {
    "title": "Avo Public API",
    "version": "1.2.0",
    "description": "Programmatic access to portfolio management operations on Avo.",
    "contact": {
      "name": "Avo Support",
      "url": "https://discord.gg/avodotso"
    }
  },
  "servers": [
    {
      "url": "https://api.avo.so",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "Health", "description": "Health check endpoints" },
    { "name": "Portfolio", "description": "Portfolio management endpoints" }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "/health",
        "description": "Returns the health status of the API. No authentication required.",
        "tags": ["Health"],
        "x-icon": "heartpulse",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "status": "healthy",
                  "timestamp": "2026-03-14T07:12:00.574Z",
                  "uptime": 6048.002539756
                }
              }
            }
          },
          "500": {
            "description": "Server Error",
            "content": {
              "application/json": {
                "example": {
                  "status": "error",
                  "message": "Internal server error"
                }
              }
            }
          }
        }
      }
    },
    "/ping": {
      "get": {
        "operationId": "ping",
        "summary": "/ping",
        "description": "Returns a simple pong response. Use this to verify the API is reachable. No authentication required.",
        "tags": ["Health"],
        "x-icon": "heartpulse",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "message": "pong"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "api-health",
        "summary": "/api/health",
        "description": "Authenticated health check endpoint. Returns the health status of the internal API services.",
        "tags": ["Health"],
        "x-icon": "heartpulse",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "status": "healthy",
                  "timestamp": "2026-03-14T07:12:00.574Z",
                  "services": {
                    "database": "connected",
                    "cache": "connected"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid auth token"
                }
              }
            }
          }
        }
      }
    },
    "/api/portfolio/ping": {
      "get": {
        "operationId": "portfolio-ping",
        "summary": "/api/portfolio/ping",
        "description": "Simple ping to the portfolio service to verify connectivity. Requires JWT authentication.",
        "tags": ["Portfolio"],
        "x-icon": "briefcase",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "example": "pong"
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid auth token"
                }
              }
            }
          },
          "502": {
            "description": "Bad Gateway",
            "content": {
              "application/json": {
                "example": {
                  "error": "Unable to reach upstream service"
                }
              }
            }
          }
        }
      }
    },
    "/api/portfolio/create": {
      "post": {
        "operationId": "portfolio-create",
        "summary": "/api/portfolio/create",
        "description": "Creates a new portfolio. Uses multipart/form-data to support optional image upload.\n\nThis 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.",
        "tags": ["Portfolio"],
        "x-icon": "briefcase",
        "security": [{ "bearerAuth": [] }],
        "x-docs": [
          {
            "type": "heading",
            "text": "Portfolio Types"
          },
          {
            "type": "callout",
            "variant": "info",
            "text": "The type field is currently for aesthetic purposes only. Use <code>PORTFOLIO</code> as the default option. However, if you're deciding between a standard portfolio and an index, we recommend building an <code>INDEX_PORTFOLIO</code> as it currently has better visibility on Avo."
          },
          {
            "type": "table",
            "headers": ["Type", "Description"],
            "rows": [
              ["<code>PORTFOLIO</code>", "A standard portfolio (default)"],
              ["<code>INDEX_PORTFOLIO</code>", "A portfolio that tracks a basket of tokens with dynamic rebalancing (better visibility on Avo)"]
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["name", "type"],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Name for the managed portfolio (1-30 characters). Example: My Index Portfolio",
                    "example": "My Index Portfolio",
                    "x-placeholder": "Portfolio name"
                  },
                  "type": {
                    "type": "string",
                    "enum": ["PORTFOLIO", "INDEX_PORTFOLIO"],
                    "description": "PORTFOLIO or INDEX_PORTFOLIO",
                    "example": "INDEX_PORTFOLIO",
                    "x-placeholder": "PORTFOLIO or INDEX_PORTFOLIO"
                  },
                  "beneficiary_wallet": {
                    "type": "string",
                    "description": "The wallet that will receive deployer fees. Must have some initial SOL (minimum rent exemption amount). 32-44 characters. Example: 7xkXtg2Cw87d97TXJ3DpbD5jBkheTqA83TZRuJosgAsU",
                    "x-placeholder": "Solana wallet address"
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional description for the portfolio (max 500 characters). Example: A curated index of top tokens",
                    "x-placeholder": "Portfolio description"
                  },
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Optional image file (JPEG or PNG, max 10MB)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "portfolio_id": "abc123..."
                }
              }
            }
          },
          "400": {
            "description": "Invalid request parameters",
            "content": {
              "application/json": {
                "example": {
                  "success": false,
                  "error": {
                    "source": "portfolio-service",
                    "code": "VALIDATION_ERROR",
                    "message": "Name must be between 1 and 30 characters",
                    "function": "handleCreatePortfolio"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid auth token"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "example": {
                  "success": false,
                  "error": {
                    "source": "portfolio-service",
                    "code": "INTERNAL_ERROR",
                    "message": "An unexpected error occurred",
                    "function": "handleCreatePortfolio"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/portfolio/update": {
      "post": {
        "operationId": "portfolio-update",
        "summary": "/api/portfolio/update",
        "description": "Updates the target positions for an existing portfolio. Target positions define the desired allocation of tokens in the portfolio.",
        "tags": ["Portfolio"],
        "x-icon": "briefcase",
        "security": [{ "bearerAuth": [] }],
        "x-docs": [
          {
            "type": "heading",
            "text": "Allocation Rules"
          },
          {
            "type": "list",
            "items": [
              "Each <code>allocation</code> value must be between <code>0</code> and <code>1</code> (representing 0% to 100%)",
              "The sum of all allocations should equal <code>1</code> (100%)",
              "You can include 1-100 target positions (recommended to keep it under 30 positions for better UX)"
            ]
          },
          {
            "type": "heading",
            "text": "Common Token Mints"
          },
          {
            "type": "table",
            "headers": ["Token", "Mint Address"],
            "rows": [
              ["SOL (Wrapped)", "<code>So11111111111111111111111111111111111111112</code>"],
              ["AVO", "<code>GdZ9rwHyKcriLdbSzhtEFLe5MLs7Vk6AY1aE5ei7nsmP</code>"],
              ["INFRA", "<code>D1wZHkfk8d6QsCjF3NTiYHLzsZJ2Qb4Q7wBFFbGuzBLV</code>"]
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["portfolio_id", "target_positions"],
                "properties": {
                  "portfolio_id": {
                    "type": "string",
                    "description": "The unique identifier of the portfolio to update. Example: 550e8400-e29b-41d4-a716-446655440000",
                    "example": "550e8400-e29b-41d4-a716-446655440000",
                    "x-placeholder": "Portfolio ID"
                  },
                  "target_positions": {
                    "type": "array",
                    "description": "Array of target position allocations (1-100 items). Each object has mint (string) and allocation (number 0-1)",
                    "items": {
                      "type": "object",
                      "required": ["mint", "allocation"],
                      "properties": {
                        "mint": {
                          "type": "string",
                          "description": "Token mint address",
                          "x-placeholder": "Token mint address"
                        },
                        "allocation": {
                          "type": "number",
                          "description": "Allocation percentage (0.0 - 1.0)",
                          "x-placeholder": "0.0 - 1.0"
                        }
                      }
                    },
                    "x-array-defaults": [
                      { "mint": "So11111111111111111111111111111111111111112", "allocation": "0.5" },
                      { "mint": "GdZ9rwHyKcriLdbSzhtEFLe5MLs7Vk6AY1aE5ei7nsmP", "allocation": "0.3" },
                      { "mint": "D1wZHkfk8d6QsCjF3NTiYHLzsZJ2Qb4Q7wBFFbGuzBLV", "allocation": "0.2" }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Portfolio updated successfully",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "message": "Portfolio target positions updated successfully"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request parameters",
            "content": {
              "application/json": {
                "example": {
                  "success": false,
                  "error": {
                    "source": "portfolio-service",
                    "code": "VALIDATION_ERROR",
                    "message": "Invalid portfolio ID",
                    "function": "handleUpdatePortfolio"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid auth token"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "example": {
                  "success": false,
                  "error": {
                    "source": "portfolio-service",
                    "code": "INTERNAL_ERROR",
                    "message": "An unexpected error occurred",
                    "function": "handleUpdatePortfolio"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  }
}
