{
  "openapi": "3.1.1",
  "info": {
    "title": "TrackMyStack API",
    "description": "Read and change a TrackMyStack account: portfolios, holdings and their dated positions, and\nthe analysis the app shows.\n\n**This is a dated-position API, not a transaction API.** A holding's history is a list of\ndated positions, each the total held on that date - not a buy or a sell. Writing a position\nreplaces what stood on that date; to record a sale, write the smaller total. Cost basis is\nworked out from the difference between consecutive positions.\n\n**Authentication.** Create a personal access token in the app under Account, then Connected\napps, and send it as `Authorization: Bearer tms_pat_…`. A read-only token can call every GET.\n\n**Money.** Every amount carries its currency. A listing's quote can be in a minor unit:\nLondon lines quote in GBX, pence, so check each price's currency.\n\n**Subscription.** Net worth, portfolios and holdings, positions, market search and every\nwrite work on any account, priced at the last close. History, movers, allocation, dividends\nand goals need a TrackMyStack subscription, and prices are live with one.\n\n**Rate limits.** Each account has 120 requests a minute, shared with its assistant\nconnections, and 30 a minute of those for net worth history, allocation, dividends and\ngoals. Past either, a request is refused with 429 and a `Retry-After` header.\n\n**Errors** are RFC 9457 problem details with a stable `code` and the `traceId` to quote\nwhen reporting one. **Lists** return `items` and a `nextCursor` to pass back as `cursor`\nuntil it is null. **Conflicts:** send a holding's `revision` as `expectedRevision` and a\nwrite is refused with 409 `revision_conflict` if the holding changed since. **Creates**\ntake an `id` you choose, so sending one twice is refused with 409 `already_exists`.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.trackmystack.app"
    }
  ],
  "paths": {
    "/rest/v1/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Who the token belongs to and what it may do: call this first.",
        "operationId": "getAccount",
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/market/assets": {
      "get": {
        "tags": [
          "Market"
        ],
        "summary": "Search the supported-asset catalog. A candidate's assetId is what createHolding takes.",
        "operationId": "searchMarketAssets",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Ticker, name, ISIN or other identifier.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "description": "stocks, etf, fund, crypto or commodities. Omit to search all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarketAssetList"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/movers": {
      "get": {
        "tags": [
          "Analysis"
        ],
        "summary": "The holdings whose value moved most since the previous close.",
        "operationId": "getMovers",
        "parameters": [
          {
            "name": "portfolioId",
            "in": "query",
            "description": "One portfolio instead of every net-worth portfolio.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoverList"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/allocation": {
      "get": {
        "tags": [
          "Analysis"
        ],
        "summary": "What the portfolios are made of, by sector, region, asset type and currency.",
        "operationId": "getAllocation",
        "parameters": [
          {
            "name": "portfolioId",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AllocationResponse"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/dividends": {
      "get": {
        "tags": [
          "Analysis"
        ],
        "summary": "Dividends received, declared and yielded.",
        "operationId": "getDividends",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "First day, yyyy-MM-dd. The twelve months up to to when omitted.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, yyyy-MM-dd. Today when omitted or later. At most ten years after from.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "portfolioId",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DividendsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/goals": {
      "get": {
        "tags": [
          "Analysis"
        ],
        "summary": "Savings goals and how far along each is.",
        "operationId": "listGoals",
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalList"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/net-worth": {
      "get": {
        "tags": [
          "Net worth"
        ],
        "summary": "Net worth now, and its change since the previous close.",
        "operationId": "getNetWorth",
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetWorthResponse"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/net-worth/history": {
      "get": {
        "tags": [
          "Net worth"
        ],
        "summary": "What the account, or one portfolio, was worth over a period.",
        "operationId": "getNetWorthHistory",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "First day, yyyy-MM-dd.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, yyyy-MM-dd. Today when omitted or later.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "granularity",
            "in": "query",
            "description": "endpoints, daily, weekly or monthly. At most 400 points.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "portfolioId",
            "in": "query",
            "description": "One portfolio instead of the whole account.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetWorthHistoryResponse"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/portfolios": {
      "get": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Every portfolio, valued in the home currency.",
        "operationId": "listPortfolios",
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Create an empty portfolio under an id you choose.",
        "operationId": "createPortfolio",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePortfolioRequest"
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/portfolios/{id}": {
      "get": {
        "tags": [
          "Portfolios"
        ],
        "summary": "One portfolio, valued in the home currency.",
        "operationId": "getPortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Change a portfolio's name, currency, ownership or inclusion in net worth.",
        "operationId": "updatePortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePortfolioRequest"
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Delete a portfolio and its holdings, taking them out of history too, until restored.",
        "operationId": "deletePortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/portfolios/{id}/archive": {
      "post": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Record each holding in a portfolio as sold today and archive it all, keeping its history.",
        "operationId": "archivePortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/portfolios/{id}/unarchive": {
      "post": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Clear a portfolio's archive flag. Its holdings stay archived until each is unarchived.",
        "operationId": "unarchivePortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/portfolios/{id}/restore": {
      "post": {
        "tags": [
          "Portfolios"
        ],
        "summary": "Bring back a deleted portfolio alone; restore the holdings deleted with it one by one.",
        "operationId": "restorePortfolio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortfolioWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings": {
      "get": {
        "tags": [
          "Holdings"
        ],
        "summary": "Holdings across every portfolio, or one, a page at a time.",
        "operationId": "listHoldings",
        "parameters": [
          {
            "name": "portfolioId",
            "in": "query",
            "description": "Only this portfolio's holdings.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "description": "Archived holdings are left out unless this is true.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Holdings"
        ],
        "summary": "Create a holding under an id you choose, with its first positions.",
        "operationId": "createHolding",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHoldingRequest"
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}": {
      "get": {
        "tags": [
          "Holdings"
        ],
        "summary": "One holding, fully valued.",
        "operationId": "getHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Holdings"
        ],
        "summary": "Change how a holding's cost basis is tracked.",
        "operationId": "updateHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "description": "Refuse the write if the holding has changed since this revision.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHoldingRequest"
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Holdings"
        ],
        "summary": "Delete a holding, taking it out of history too, until it is restored.",
        "operationId": "deleteHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}/positions": {
      "get": {
        "tags": [
          "Holdings"
        ],
        "summary": "A holding's dated positions, newest first.",
        "operationId": "listHoldingPositions",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Earliest date to include, yyyy-MM-dd.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Latest date to include, yyyy-MM-dd.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PositionList"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}/archive": {
      "post": {
        "tags": [
          "Holdings"
        ],
        "summary": "Record a holding as sold today and archive it, keeping its history.",
        "operationId": "archiveHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}/unarchive": {
      "post": {
        "tags": [
          "Holdings"
        ],
        "summary": "Clear a holding's archive flag, leaving its positions as they are.",
        "operationId": "unarchiveHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}/restore": {
      "post": {
        "tags": [
          "Holdings"
        ],
        "summary": "Bring back a deleted holding, once its portfolio is not deleted.",
        "operationId": "restoreHolding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/rest/v1/holdings/{id}/positions/{date}": {
      "put": {
        "tags": [
          "Holdings"
        ],
        "summary": "Record the total held on one date, replacing whatever stood there.",
        "operationId": "setHoldingPosition",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "path",
            "description": "yyyy-MM-dd, no later than tomorrow.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "description": "Refuse the write if the holding has changed since this revision.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PositionValues"
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Holdings"
        ],
        "summary": "Remove the position recorded on one date.",
        "operationId": "deleteHoldingPosition",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedRevision",
            "in": "query",
            "description": "Refuse the write if the holding has changed since this revision.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HoldingWriteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AccountResponse": {
        "required": [
          "homeCurrency",
          "hasSubscription",
          "canWrite"
        ],
        "type": "object",
        "properties": {
          "homeCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency totals are reported in. Null for an account the app has never set one for."
          },
          "hasSubscription": {
            "type": "boolean",
            "description": "Whether the subscription-only endpoints answer."
          },
          "canWrite": {
            "type": "boolean",
            "description": "Whether this token may make changes."
          }
        }
      },
      "AllocationResponse": {
        "required": [
          "currency",
          "totalValue",
          "portfolios",
          "selection",
          "holdingCount",
          "bySector",
          "byRegion",
          "byAssetType",
          "byCurrency",
          "isComplete",
          "warnings",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalValue": {
            "description": "The positive value classified. Liabilities are left out, and portfolios kept out of net worth count, so it is not net worth.",
            "$ref": "#/components/schemas/Money"
          },
          "portfolios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SelectedPortfolio"
            },
            "description": "The portfolios covered, and the only ones."
          },
          "selection": {
            "type": "string",
            "description": "\"requested\" when portfolios were named, otherwise \"all\"."
          },
          "holdingCount": {
            "type": "integer",
            "description": "Holdings in the breakdown: those with a positive value.",
            "format": "int32"
          },
          "bySector": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllocationSlice"
            }
          },
          "byRegion": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllocationSlice"
            }
          },
          "byAssetType": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllocationSlice"
            }
          },
          "byCurrency": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllocationSlice"
            }
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when some holding could not be valued or classified."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "What the portfolios are made of, by sector, region, asset type and currency at once. A holding\ncan be split across buckets, so a global fund counts partly in several regions."
      },
      "AllocationSlice": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "value": {
            "$ref": "#/components/schemas/Money"
          },
          "percent": {
            "type": "number",
            "description": "Share of what this dimension could attribute, in percentage points - so the percents sum to 100 even when the slice values total less than totalValue.",
            "format": "double"
          }
        }
      },
      "CreateHoldingRequest": {
        "required": [
          "id",
          "portfolioId",
          "name",
          "positions"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ],
            "description": "A UUID of your choosing. The same create sent again is refused as already_exists rather than made twice.",
            "format": "uuid"
          },
          "portfolioId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "positions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatedPosition"
            },
            "description": "At least one. A unit price on a market-linked create tracks cost basis per transaction, as the app does."
          },
          "assetId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The catalog asset to track, by the assetId /market/assets returned. Omit for a manual holding. A ticker is not accepted: a venue can reassign one.",
            "format": "int32"
          },
          "currency": {
            "type": [
              "null",
              "string"
            ],
            "description": "A manual holding's currency. A market-linked holding takes its listing's."
          },
          "costBasisTracking": {
            "type": [
              "null",
              "string"
            ],
            "description": "\"none\", \"total\" or \"transaction\". A manual holding cannot use \"transaction\"."
          },
          "totalCostBasis": {
            "type": [
              "null",
              "number"
            ],
            "description": "What the whole position cost, in its trackingCurrency. Goes only with costBasisTracking \"total\".",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "CreatePortfolioRequest": {
        "required": [
          "id",
          "name",
          "currency"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ],
            "description": "A UUID of your choosing. The same create sent again is refused as already_exists rather than made twice.",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "description": "A supported three-letter code, usually the home currency."
          },
          "includeInNetWorth": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Defaults to true."
          },
          "ownershipPercent": {
            "type": [
              "null",
              "integer"
            ],
            "description": "A whole number from 0 to 100. Defaults to 100.",
            "format": "int32"
          }
        }
      },
      "DatedPosition": {
        "required": [
          "date",
          "totalQuantityHeld"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "yyyy-MM-dd, no later than tomorrow."
          },
          "totalQuantityHeld": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "debt": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "fee": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "DividendIncomeSummary": {
        "type": "object",
        "properties": {
          "total": {
            "description": "Dividends already paid within the period. A payment counts on its payment date where the issuer gave one, otherwise on its ex-dividend date.",
            "$ref": "#/components/schemas/Money"
          },
          "previousPeriodTotal": {
            "description": "The same figure for the same number of months immediately before - or days, when the period is not whole months - so a year compares against the year before it and a month against the month before.",
            "$ref": "#/components/schemas/Money"
          },
          "changePercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "Change against the previous period, as a percentage of it. Null when that period paid nothing, because a rise from zero has no percentage.",
            "format": "double"
          },
          "byMonth": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DividendMonth"
            },
            "description": "Every month the period touches, oldest first, including months that paid nothing. Use it for when income arrived rather than only how much. The first and last months are partial when the period does not start or end on a month boundary, so a low first month is not a month that paid little."
          }
        }
      },
      "DividendMonth": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "description": "The month, as yyyy-MM."
          },
          "amount": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "DividendPeriod": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "DividendsResponse": {
        "required": [
          "currency",
          "period",
          "portfolios",
          "selection",
          "income",
          "upcoming",
          "yield",
          "isComplete",
          "warnings",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "period": {
            "description": "The period income covers. The comparison is the same length immediately before it.",
            "$ref": "#/components/schemas/DividendPeriod"
          },
          "portfolios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SelectedPortfolio"
            }
          },
          "selection": {
            "type": "string",
            "description": "\"requested\" when portfolios were named, otherwise \"all\"."
          },
          "income": {
            "$ref": "#/components/schemas/DividendIncomeSummary"
          },
          "upcoming": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingDividend"
            },
            "description": "Declared payments, expected rather than received."
          },
          "yield": {
            "$ref": "#/components/schemas/DividendYieldResponse"
          },
          "isComplete": {
            "type": "boolean"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Dividends received in a period against the period before it, payments declared but not yet\npaid, and what each holding yields. Crypto and manually tracked holdings are not covered."
      },
      "DividendYieldResponse": {
        "required": [
          "weightedPercent",
          "measuredMarketValue",
          "totalMarketValue",
          "byHolding",
          "holdingCount",
          "receivedFromUnlistedHoldings"
        ],
        "type": "object",
        "properties": {
          "weightedPercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "Value-weighted, over the holdings whose yield is known only. Null when none is.",
            "format": "double"
          },
          "measuredMarketValue": {
            "description": "The part of totalMarketValue that weightedPercent covers. Where smaller, the rest has an unknown yield.",
            "$ref": "#/components/schemas/Money"
          },
          "totalMarketValue": {
            "description": "Every holding counted, whether or not its yield is known.",
            "$ref": "#/components/schemas/Money"
          },
          "byHolding": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HoldingDividendYield"
            }
          },
          "holdingCount": {
            "type": "integer",
            "description": "How many holdings byHolding covers. More than it lists when the list was capped; the smallest positions are left out.",
            "format": "int32"
          },
          "receivedFromUnlistedHoldings": {
            "description": "Income in the period from holdings not in byHolding, mostly ones sold since.",
            "$ref": "#/components/schemas/Money"
          }
        },
        "description": "Always the trailing twelve months over today's value, whatever period was asked for."
      },
      "GoalList": {
        "required": [
          "items",
          "nextCursor",
          "homeCurrency",
          "priceLatency",
          "isComplete",
          "warnings",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GoalSummary"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Always null."
          },
          "homeCurrency": {
            "type": [
              "null",
              "string"
            ]
          },
          "priceLatency": {
            "type": "string"
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when a linked portfolio could not be fully priced; each goal has its own warnings too."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "The user's savings goals, each valued from the share of the portfolios linked to it."
      },
      "GoalPortfolioLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "allocationPercent": {
            "type": "number",
            "description": "How much of this portfolio counts toward the goal, in percentage points. A portfolio shared between goals is split by these.",
            "format": "double"
          },
          "value": {
            "description": "This portfolio's contribution to the goal: its whole value in the home currency, already reduced by allocationPercent.",
            "$ref": "#/components/schemas/Money"
          },
          "includedInNetWorth": {
            "type": "boolean",
            "description": "False when the portfolio is one the user keeps out of their net worth. It still counts toward the goal."
          }
        }
      },
      "GoalSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "goalType": {
            "type": [
              "null",
              "string"
            ],
            "description": "What the goal is for: Retirement, House, Car, Vacation, College, Debt, Emergency, or Other."
          },
          "target": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What the user is aiming for. Its currency is the one recorded when the goal was created; check warnings before comparing it with currentValue."
          },
          "currentValue": {
            "description": "What the linked portfolios are worth now, in the home currency, counting each one's allocation share and the user's ownership of it.",
            "$ref": "#/components/schemas/Money"
          },
          "startValue": {
            "description": "What the same portfolios were worth on startedAt. Equal to currentValue when nothing could be valued that far back: for a goal measured against its target that changes nothing, and for one that starts in debt or aims at zero it makes progress read as zero rather than as a loss.",
            "$ref": "#/components/schemas/Money"
          },
          "progressPercent": {
            "type": "number",
            "description": "How far along the goal is, in percentage points, clamped to 0-100. Measured against the target for an ordinary savings goal, and along the whole distance from startValue to the target for one that begins in debt or aims at zero.",
            "format": "double"
          },
          "remainingAmount": {
            "description": "What is still to be found, in the home currency. Zero once the goal is reached.",
            "$ref": "#/components/schemas/Money"
          },
          "projectedValue": {
            "description": "What the goal is worth on its target date if the assumed return holds and the monthly contribution keeps coming. Contributions pay down any debt in the linked portfolios before they are invested.",
            "$ref": "#/components/schemas/Money"
          },
          "status": {
            "type": "string",
            "description": "'completed', 'ahead', 'on-track' or 'behind', decided by where projectedValue lands against the target rather than by progress so far - a goal at 10% with years to run can still be on track."
          },
          "daysRemaining": {
            "type": "integer",
            "description": "Days until the target date, floored at zero. Zero means the date has passed, which status does not say on its own.",
            "format": "int32"
          },
          "monthlyDeposit": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What the user puts in each month. Recorded in the same currency as the target."
          },
          "assumedAnnualReturnPercent": {
            "type": "number",
            "description": "The annual return the projection assumes, in percentage points. A goal that never set its own is projected at 7. It grows only what the linked portfolios own: debts are neither grown nor charged interest, and contributions pay debts down first, so a goal made only of debts projects as a straight line.",
            "format": "double"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ],
            "description": "Whatever the user wrote about this goal. Often empty."
          },
          "linkedPortfolios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GoalPortfolioLink"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What is wrong with this goal in particular - a linked portfolio that no longer exists, or a target recorded in another currency. A holding that could not be priced is reported in the top-level warnings instead, so an empty list here does not make the figures complete."
          },
          "targetDateUtc": {
            "type": "string",
            "format": "date-time"
          },
          "startedAtUtc": {
            "type": "string",
            "description": "The date progress is measured from. The user picks it and may backdate it, so it is not necessarily when the goal was created.",
            "format": "date-time"
          }
        }
      },
      "HoldingDividendYield": {
        "type": "object",
        "properties": {
          "holdingId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "portfolioId": {
            "type": "string",
            "format": "uuid"
          },
          "portfolioName": {
            "type": "string"
          },
          "fullTicker": {
            "type": "string"
          },
          "marketValue": {
            "description": "The whole position's current worth, with portfolio ownership not applied.",
            "$ref": "#/components/schemas/Money"
          },
          "receivedInPeriod": {
            "description": "What this holding actually paid the user within the period above - which is not what the yield is calculated from, since a holding bought halfway through the year received half a year of dividends but yields a whole year's worth.",
            "$ref": "#/components/schemas/Money"
          },
          "annualPerShare": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What the asset paid per share over the trailing twelve months, whether or not the user held it then, converted to the user's currency. Null whenever percent is, and for the same reason."
          },
          "percent": {
            "type": [
              "null",
              "number"
            ],
            "description": "The trailing twelve-month per-share figure over the current share price, in percentage points. Null when the holding could not be priced or a rate was missing, never zero - zero means it paid out nothing, which is also what an accumulating share class that reinvests its income shows: this is cash paid, not total return.",
            "format": "double"
          }
        }
      },
      "HoldingList": {
        "required": [
          "items",
          "nextCursor",
          "totalCount",
          "homeCurrency",
          "priceLatency",
          "asOfUtc",
          "isComplete"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HoldingResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalCount": {
            "type": "integer",
            "description": "Holdings matching the filter, across every page.",
            "format": "int32"
          },
          "homeCurrency": {
            "type": [
              "null",
              "string"
            ]
          },
          "priceLatency": {
            "type": "string",
            "description": "\"live\" with a subscription, otherwise \"endOfDay\"."
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when a holding on this page could not be fully valued."
          }
        }
      },
      "HoldingPositionEntry": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "The UTC date this position took effect, as yyyy-MM-dd."
          },
          "quantity": {
            "type": "number",
            "description": "Units held on this date. For a manual holding the quantity is the value itself, in the holding's own currency.",
            "format": "double"
          },
          "debt": {
            "type": "number",
            "format": "double"
          },
          "netQuantity": {
            "type": "number",
            "description": "Quantity minus debt, which is what valuation uses.",
            "format": "double"
          },
          "quantityChange": {
            "type": "number",
            "description": "Change in quantity from the position immediately before this one in the holding's full history - which may be older than anything returned when from is set. It equals the whole quantity only for the holding's very first position. Cost basis is computed from these differences, so inserting a position between two others changes what the later one means.",
            "format": "double"
          },
          "unitPrice": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceQuote"
              }
            ],
            "description": "The unit price recorded for this change — what was paid or received per unit, not a running average. Null when none was recorded."
          },
          "fee": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The transaction fee recorded on this change. Null when none was recorded."
          }
        }
      },
      "HoldingResponse": {
        "required": [
          "id",
          "portfolioId",
          "name",
          "description",
          "assetType",
          "assetOrLiability",
          "isManual",
          "isArchived",
          "fullTicker",
          "assetId",
          "listingCurrency",
          "trackingCurrency",
          "priceCurrency",
          "revision",
          "positionDate",
          "totalQuantityHeld",
          "debt",
          "netQuantity",
          "price",
          "value",
          "valueInHomeCurrency",
          "portfolioWeightPercent",
          "costBasisTrackingType",
          "costBasis",
          "costBasisInHomeCurrency",
          "unrealisedGain",
          "unrealisedGainInHomeCurrency",
          "unrealisedGainPercent",
          "warnings"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "portfolioId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "assetType": {
            "type": "string",
            "description": "How the holding is priced: every market-listed security, funds included, is \"Stock\"."
          },
          "assetOrLiability": {
            "type": "string",
            "description": "\"asset\", \"liability\" or \"asset-liability\"."
          },
          "isManual": {
            "type": "boolean",
            "description": "True for a holding valued by its quantity alone, with no market price."
          },
          "isArchived": {
            "type": "boolean"
          },
          "fullTicker": {
            "type": [
              "null",
              "string"
            ],
            "description": "The current symbol of the catalog asset tracked, such as \"VOD.LSE\". Null for a manual holding."
          },
          "assetId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The catalog asset's stable id, as /market/assets reports it. Null for a manual holding.",
            "format": "int32"
          },
          "listingCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency the venue quotes in. LSE lines quote in GBX, pence."
          },
          "trackingCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency value, costBasis and fees are in."
          },
          "priceCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency price and recorded unit prices are in, pence for a GBX listing."
          },
          "revision": {
            "type": "integer",
            "description": "Send as expectedRevision on a write that depends on what is here now.",
            "format": "int64"
          },
          "positionDate": {
            "type": [
              "null",
              "string"
            ],
            "description": "The date of the position in force, yyyy-MM-dd."
          },
          "totalQuantityHeld": {
            "type": "number",
            "description": "Before any debt is taken off. For a manual holding, its value.",
            "format": "double"
          },
          "debt": {
            "type": "number",
            "description": "Borrowed against the holding, such as a mortgage.",
            "format": "double"
          },
          "netQuantity": {
            "type": "number",
            "description": "totalQuantityHeld minus debt, which is what is valued.",
            "format": "double"
          },
          "price": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceQuote"
              }
            ]
          },
          "value": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ]
          },
          "valueInHomeCurrency": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Null when no exchange rate was available."
          },
          "portfolioWeightPercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "Share of the portfolio's positive value. Null for a liability.",
            "format": "double"
          },
          "costBasisTrackingType": {
            "type": "string",
            "description": "\"None\", \"Total\" or \"Transaction\"."
          },
          "costBasis": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ]
          },
          "costBasisInHomeCurrency": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ]
          },
          "unrealisedGain": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ]
          },
          "unrealisedGainInHomeCurrency": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ]
          },
          "unrealisedGainPercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "In trackingCurrency, so it leaves out currency moves.",
            "format": "double"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Why a figure is missing or cannot be relied on. Empty when there is nothing to report."
          }
        },
        "description": "One holding as it stands. Every figure is the whole position: scale by the portfolio's\nownershipPercent before reconciling with net worth."
      },
      "HoldingWriteResponse": {
        "required": [
          "id",
          "operation",
          "name",
          "currency",
          "previousNetQuantity",
          "newNetQuantity",
          "writtenDates",
          "removedDates",
          "restated",
          "revision",
          "notes"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "operation": {
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "previousNetQuantity": {
            "type": [
              "null",
              "number"
            ],
            "description": "Net quantity before the write. Null for a create, a delete and a restore.",
            "format": "double"
          },
          "newNetQuantity": {
            "type": [
              "null",
              "number"
            ],
            "description": "The newest position less its debt. Null for a delete and a restore.",
            "format": "double"
          },
          "writtenDates": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "removedDates": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "restated": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RestatedPosition"
            },
            "description": "Positions not written whose meaning changed anyway: cost basis comes from the difference between consecutive positions."
          },
          "revision": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The revision now. Send it as expectedRevision on the next write that depends on this state.",
            "format": "int64"
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What was stored but will not do what may be expected, such as prices that do not count toward cost basis."
          }
        },
        "description": "What a write did, so a caller can say what changed."
      },
      "MarketAssetCandidate": {
        "type": "object",
        "properties": {
          "assetId": {
            "type": "integer",
            "description": "The catalog asset's stable id. Use it to create a holding: a venue can rename a symbol or reassign it to another company, this never changes.",
            "format": "int32"
          },
          "fullTicker": {
            "type": "string",
            "description": "The asset's current symbol with its venue, for example \"MSFT.US\". For display and for telling candidates apart."
          },
          "symbol": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "category": {
            "type": "string"
          },
          "instrumentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "exchange": {
            "type": "string"
          },
          "country": {
            "type": [
              "null",
              "string"
            ]
          },
          "isin": {
            "type": [
              "null",
              "string"
            ]
          },
          "listingCurrency": {
            "type": [
              "null",
              "string"
            ]
          },
          "latestPrice": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceQuote"
              }
            ]
          }
        }
      },
      "MarketAssetList": {
        "required": [
          "items",
          "nextCursor",
          "totalMatches"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MarketAssetCandidate"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Always null: narrow the query rather than paging."
          },
          "totalMatches": {
            "type": "integer",
            "description": "Everything the query matched; more than items holds when the limit cut it short.",
            "format": "int32"
          }
        },
        "description": "Candidates from the supported-asset catalog, best match first. Never picks one for you: create a\nmarket-linked holding with a candidate's assetId. Its fullTicker is the current symbol, for display."
      },
      "Money": {
        "required": [
          "amount",
          "currency"
        ],
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "format": "double"
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "MoverList": {
        "required": [
          "items",
          "nextCursor",
          "scope",
          "portfolioId",
          "portfolioName",
          "valuationCurrency",
          "isComplete",
          "warnings",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortfolioMover"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Always null."
          },
          "scope": {
            "type": "string",
            "description": "\"total\" across net-worth portfolios, or \"portfolio\" within one."
          },
          "portfolioId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "portfolioName": {
            "type": [
              "null",
              "string"
            ]
          },
          "valuationCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The home currency across portfolios, the portfolio's own within one."
          },
          "isComplete": {
            "type": "boolean"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "The holdings whose value moved most since the previous UTC close, largest first."
      },
      "NetWorthHistoryResponse": {
        "required": [
          "points",
          "currency",
          "scope",
          "portfolioId",
          "portfolioName",
          "granularity",
          "change",
          "changePercent",
          "includedInNetWorth",
          "isComplete",
          "warnings",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NetWorthPoint"
            },
            "description": "Oldest first. End-of-day closes, except a final point dated today, which is live."
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "scope": {
            "type": "string",
            "description": "\"total\" for the account, \"portfolio\" when narrowed to one."
          },
          "portfolioId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "portfolioName": {
            "type": [
              "null",
              "string"
            ]
          },
          "granularity": {
            "type": "string",
            "description": "endpoints, daily, weekly or monthly; chosen from the period when not asked for."
          },
          "change": {
            "description": "Last value minus first, money paid in or out included: not an investment return.",
            "$ref": "#/components/schemas/Money"
          },
          "changePercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "Null when the start was zero or unavailable, or the value crossed zero.",
            "format": "double"
          },
          "includedInNetWorth": {
            "type": "boolean",
            "description": "False when the series is for a portfolio kept out of net worth."
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when some point left a holding out; a sharp move there may not be real."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "What the account, or one portfolio, was worth over a period."
      },
      "NetWorthPoint": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "The UTC date this value is for, as yyyy-MM-dd."
          },
          "value": {
            "$ref": "#/components/schemas/Money"
          },
          "isLive": {
            "type": "boolean",
            "description": "True for a point valued at current prices rather than at that day's close. Only ever the last point, when the period runs to today."
          }
        }
      },
      "NetWorthResponse": {
        "required": [
          "netWorth",
          "todayChange",
          "todayChangePercent",
          "priceLatency",
          "asOfUtc",
          "isComplete",
          "warnings"
        ],
        "type": "object",
        "properties": {
          "netWorth": {
            "description": "In the home currency, counting only portfolios included in net worth, each scaled by its ownership.",
            "$ref": "#/components/schemas/Money"
          },
          "todayChange": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Since the previous close. Null when priced at the last close, and when yesterday is unavailable."
          },
          "todayChangePercent": {
            "type": [
              "null",
              "number"
            ],
            "description": "Null when yesterday's net worth was zero or unavailable, or net worth crossed zero since.",
            "format": "double"
          },
          "priceLatency": {
            "type": "string",
            "description": "\"live\" with a subscription, otherwise \"endOfDay\": every holding at its last close."
          },
          "asOfUtc": {
            "type": "string",
            "description": "When the newest price used was observed.",
            "format": "date-time"
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when some holding could not be valued; the total then leaves it out and warnings names it."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PortfolioList": {
        "required": [
          "items",
          "nextCursor",
          "priceLatency",
          "asOfUtc",
          "isComplete",
          "warnings"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortfolioResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Always null: every portfolio comes back at once."
          },
          "priceLatency": {
            "type": "string",
            "description": "\"live\" with a subscription, otherwise \"endOfDay\"."
          },
          "asOfUtc": {
            "type": "string",
            "format": "date-time"
          },
          "isComplete": {
            "type": "boolean",
            "description": "False when some holding could not be valued; warnings names it."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PortfolioMover": {
        "type": "object",
        "properties": {
          "fullTicker": {
            "type": "string",
            "description": "The asset that moved, for example \"MSFT.US\". Across portfolios one underlying asset is one mover, aggregated by this."
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "latestPrice": {
            "$ref": "#/components/schemas/PriceQuote"
          },
          "priceChangePercent": {
            "type": "number",
            "description": "Price change against the previous UTC close, expressed in percentage points, in the listing's own currency - so it leaves out currency moves, which valueChange includes, even when latestPrice is converted into the currency the user tracks the holding in.",
            "format": "double"
          },
          "valueChange": {
            "description": "Change in what the holding is worth since the previous UTC close, in valuationCurrency: the home currency across portfolios, the portfolio's own currency within one. It includes currency moves and any change in quantity, so it can differ from priceChangePercent even in sign. Its sign is the direction: movers too small to register at the currency's precision are left out rather than reported as no change.",
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "PortfolioResponse": {
        "required": [
          "id",
          "name",
          "currency",
          "value",
          "ownershipPercent",
          "includedInNetWorth",
          "isArchived",
          "holdingCount"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "description": "The portfolio's own currency, which can differ from the home currency value is in."
          },
          "value": {
            "description": "In the home currency, already scaled by ownershipPercent.",
            "$ref": "#/components/schemas/Money"
          },
          "ownershipPercent": {
            "type": "number",
            "description": "How much of the portfolio the user owns, 0 to 100. Holding figures are whole positions; scale them by this.",
            "format": "double"
          },
          "includedInNetWorth": {
            "type": "boolean",
            "description": "False for a portfolio the user keeps out of net worth. It is still listed and valued."
          },
          "isArchived": {
            "type": "boolean",
            "description": "A label only, usually for a closed account: whatever it still holds counts as before."
          },
          "holdingCount": {
            "type": "integer",
            "description": "Holdings that are not archived.",
            "format": "int32"
          }
        }
      },
      "PortfolioWriteResponse": {
        "required": [
          "id",
          "operation",
          "name",
          "currency",
          "includedInNetWorth",
          "ownershipPercent",
          "isArchived",
          "holdingIds"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "operation": {
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "includedInNetWorth": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "ownershipPercent": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "isArchived": {
            "type": "boolean"
          },
          "holdingIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "After an archive, the holdings it archived; after a delete, the holdings deleted with it, which restoring the portfolio does not bring back."
          }
        },
        "description": "What a write did. Settings are reported as stored, and null after an archive, a delete or a restore."
      },
      "PositionList": {
        "required": [
          "items",
          "nextCursor",
          "totalCount",
          "holdingId",
          "portfolioId",
          "currency",
          "trackingCurrency",
          "priceCurrency",
          "costBasisTrackingType",
          "revision"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HoldingPositionEntry"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalCount": {
            "type": "integer",
            "description": "Positions in the requested window, across every page.",
            "format": "int32"
          },
          "holdingId": {
            "type": "string",
            "format": "uuid"
          },
          "portfolioId": {
            "type": "string",
            "format": "uuid"
          },
          "currency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The holding's own currency."
          },
          "trackingCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency fees are in."
          },
          "priceCurrency": {
            "type": [
              "null",
              "string"
            ],
            "description": "The currency recorded unit prices are in."
          },
          "costBasisTrackingType": {
            "type": "string",
            "description": "Unit prices count toward cost basis only under \"Transaction\"."
          },
          "revision": {
            "type": "integer",
            "description": "The holding's revision. Send it as expectedRevision on a write that depends on these positions.",
            "format": "int64"
          }
        },
        "description": "The holding's dated positions, newest first. Each is the total held from its date until the\nnext one, not a transaction: writing a position replaces what stood on that date."
      },
      "PositionValues": {
        "required": [
          "totalQuantityHeld"
        ],
        "type": "object",
        "properties": {
          "totalQuantityHeld": {
            "type": [
              "null",
              "number"
            ],
            "description": "Units held, before any debt. For a manual holding, its value in its own currency.",
            "format": "double"
          },
          "debt": {
            "type": [
              "null",
              "number"
            ],
            "description": "Borrowed against a manual holding, such as a mortgage. Omit to keep what was recorded.",
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "description": "Paid or received per unit for this change, in the holding's priceCurrency (pence for a GBX listing). Omit to keep what was recorded.",
            "format": "double"
          },
          "fee": {
            "type": [
              "null",
              "number"
            ],
            "description": "Fee on this change, in the holding's trackingCurrency. Omit to keep what was recorded.",
            "format": "double"
          }
        },
        "description": "The total held on one date - not a change. Writing it replaces whatever stood on that date."
      },
      "PriceQuote": {
        "required": [
          "amount",
          "currency",
          "asOfUtc"
        ],
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "format": "double"
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "asOfUtc": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RestatedPosition": {
        "required": [
          "date",
          "previousQuantityChange",
          "newQuantityChange"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string"
          },
          "previousQuantityChange": {
            "type": "number",
            "format": "double"
          },
          "newQuantityChange": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "SelectedPortfolio": {
        "required": [
          "id",
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "UpcomingDividend": {
        "type": "object",
        "properties": {
          "holdingId": {
            "type": "string",
            "format": "uuid"
          },
          "holdingName": {
            "type": [
              "null",
              "string"
            ]
          },
          "portfolioId": {
            "type": "string",
            "format": "uuid"
          },
          "portfolioName": {
            "type": "string"
          },
          "fullTicker": {
            "type": "string"
          },
          "exDate": {
            "type": "string",
            "description": "The date the holding had to be held by to qualify, which is the date the quantity below was taken on.",
            "format": "date"
          },
          "paymentDate": {
            "type": "string",
            "description": "When it is expected to be paid. Equal to the ex-dividend date where the issuer declared no payment date.",
            "format": "date"
          },
          "quantityHeld": {
            "type": "number",
            "description": "The whole position held on the ex-dividend date, with portfolio ownership not applied.",
            "format": "double"
          },
          "perShare": {
            "description": "Declared dividend per share, in the currency the issuer declared it in - which is often not the currency the share is quoted in.",
            "$ref": "#/components/schemas/Money"
          },
          "amount": {
            "description": "Per share times the quantity held, converted to the user's currency. Expected rather than received: an issuer can still change or cancel it.",
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "UpdateHoldingRequest": {
        "required": [
          "costBasisTracking"
        ],
        "type": "object",
        "properties": {
          "costBasisTracking": {
            "type": "string"
          },
          "totalCostBasis": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        },
        "description": "How cost basis is tracked from now on. Positions are written one date at a time under /positions."
      },
      "UpdatePortfolioRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "currency": {
            "type": [
              "null",
              "string"
            ]
          },
          "includeInNetWorth": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "ownershipPercent": {
            "type": [
              "null",
              "integer"
            ],
            "description": "A whole number from 0 to 100.",
            "format": "int32"
          }
        },
        "description": "Only the settings given change."
      }
    },
    "securitySchemes": {
      "apiToken": {
        "type": "http",
        "description": "A personal access token created in the app, tms_pat_….",
        "scheme": "bearer"
      }
    }
  },
  "security": [
    {
      "apiToken": [ ]
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "The account the token belongs to: its home currency, whether it has a subscription, and whether the token can write."
    },
    {
      "name": "Net worth",
      "description": "Net worth now, and its history with a subscription."
    },
    {
      "name": "Portfolios",
      "description": "Create, rename, archive, delete and restore portfolios. A portfolio's ownership share and whether it is included decide what it adds to net worth."
    },
    {
      "name": "Holdings",
      "description": "Holdings and their dated positions. A position is the total held on a date, not a trade: writing one replaces what stood on that date."
    },
    {
      "name": "Market",
      "description": "Search the catalog of supported listings. A candidate's assetId is what a market-linked holding is created with."
    },
    {
      "name": "Analysis",
      "description": "Movers, allocation, dividends and goals. Each needs a subscription."
    }
  ]
}