{
  "info": {
    "_postman_id": "b2c3d4e5-pm00-4f6a-9b0c-interpose2026",
    "name": "Interpose Portfolio Management API",
    "description": "Portfolio Management — model-based rebalancing, TWR/MWR performance, AUM billing, and white-label RIA advisor portal backend.\n\n**Pricing:** 4 bps AUM/year\n\n**Base URL:** `{{baseUrl}}`\n\n**Environment variables required:**\n- `baseUrl` — e.g. `https://api.interposehq.com`\n- `apiKey` — Bearer token or API key\n- `hmacSecret` — (optional) for HMAC-SHA256 server-to-server signing\n- `accountId` — BaaS account ID linked to the portfolio\n- `portfolioId` — set after creating a portfolio\n- `modelId` — set after creating an investment model\n- `runId` — set after triggering a rebalancing run\n- `sleeveId` — set after adding a sleeve",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      { "key": "token", "value": "{{apiKey}}", "type": "string" }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const hmacSecret = pm.environment.get('hmacSecret');",
          "if (hmacSecret) {",
          "    const timestamp = Date.now().toString();",
          "    const method = pm.request.method;",
          "    const urlObj = pm.request.url;",
          "    const path = '/' + urlObj.path.join('/');",
          "    const body = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
          "    const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);",
          "    const canonical = method + '\\n' + path + '\\n' + timestamp + '\\n' + bodyHash;",
          "    const signature = CryptoJS.HmacSHA256(canonical, hmacSecret).toString(CryptoJS.enc.Hex);",
          "    pm.request.headers.upsert({ key: 'X-VM-Key-Id', value: pm.environment.get('apiKey') });",
          "    pm.request.headers.upsert({ key: 'X-VM-Timestamp', value: timestamp });",
          "    pm.request.headers.upsert({ key: 'X-VM-Signature', value: signature });",
          "}"
        ]
      }
    }
  ],
  "variable": [
    { "key": "baseUrl", "value": "https://api.interposehq.com", "type": "string" },
    { "key": "apiKey", "value": "your-api-key-here", "type": "string" },
    { "key": "hmacSecret", "value": "", "type": "string" },
    { "key": "accountId", "value": "acct_01HV4Y2K3M5N8P9QR2ST4UVWXY", "type": "string" },
    { "key": "portfolioId", "value": "ptf_01HV4Y2K3M5N8P9QR2ST4UVWXY", "type": "string" },
    { "key": "modelId", "value": "mdl_01HV4Y2K3M5N8P9QR2ST4UVWXY", "type": "string" },
    { "key": "runId", "value": "reb_01HV4Y2K3M5N8P9QR2ST4UVWXY", "type": "string" },
    { "key": "sleeveId", "value": "slv_01HV4Y2K3M5N8P9QR2ST4UVWXY", "type": "string" }
  ],
  "item": [
    {
      "name": "Investment Models",
      "item": [
        {
          "name": "Create Model",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const body = pm.response.json();",
                  "    pm.environment.set('modelId', body.model_id);",
                  "    pm.test('model_id set', () => pm.expect(body.model_id).to.be.a('string'));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"60/40 Growth\",\n  \"description\": \"60% equities, 40% fixed income\",\n  \"weights\": [\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"SPY\" },\n      \"target_weight\": \"0.4000\"\n    },\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"QQQ\" },\n      \"target_weight\": \"0.2000\"\n    },\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"AGG\" },\n      \"target_weight\": \"0.4000\"\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/models",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "models"]
            },
            "description": "Create an investment model (target allocation). Weights must sum to exactly 1.0000."
          }
        },
        {
          "name": "List Models",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/models",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "models"]
            }
          }
        },
        {
          "name": "Get Model",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/models/{{modelId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "models", "{{modelId}}"]
            }
          }
        },
        {
          "name": "Replace Model",
          "request": {
            "method": "PUT",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"70/30 Aggressive Growth\",\n  \"description\": \"70% equities, 30% fixed income\",\n  \"weights\": [\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"SPY\" },\n      \"target_weight\": \"0.5000\"\n    },\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"QQQ\" },\n      \"target_weight\": \"0.2000\"\n    },\n    {\n      \"security\": { \"type\": \"ticker\", \"value\": \"AGG\" },\n      \"target_weight\": \"0.3000\"\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/models/{{modelId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "models", "{{modelId}}"]
            }
          }
        },
        {
          "name": "Delete Model",
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{baseUrl}}/v1/models/{{modelId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "models", "{{modelId}}"]
            },
            "description": "Delete a model. Returns 409 if any portfolios are assigned to it."
          }
        }
      ]
    },
    {
      "name": "Portfolios",
      "item": [
        {
          "name": "Create Portfolio",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const body = pm.response.json();",
                  "    pm.environment.set('portfolioId', body.portfolio_id);",
                  "    pm.test('total_value is 4dp string', () => {",
                  "        pm.expect(body.total_value).to.match(/^-?\\d+\\.\\d{4}$/);",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"account_id\": \"{{accountId}}\",\n  \"name\": \"Retirement Growth Portfolio\",\n  \"model_id\": \"{{modelId}}\",\n  \"currency\": \"USD\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios"]
            }
          }
        },
        {
          "name": "List Portfolios",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios?account_id={{accountId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios"],
              "query": [
                { "key": "account_id", "value": "{{accountId}}" },
                { "key": "model_id", "value": "{{modelId}}", "disabled": true }
              ]
            }
          }
        },
        {
          "name": "Get Portfolio",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}"]
            }
          }
        },
        {
          "name": "Update Portfolio",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Conservative Retirement Portfolio\",\n  \"model_id\": \"{{modelId}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}"]
            }
          }
        },
        {
          "name": "Delete Portfolio",
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}"]
            }
          }
        }
      ]
    },
    {
      "name": "Sleeves",
      "item": [
        {
          "name": "Add Sleeve",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const body = pm.response.json();",
                  "    pm.environment.set('sleeveId', body.sleeve_id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Core Equity\",\n  \"model_id\": \"{{modelId}}\",\n  \"target_weight\": \"0.6000\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/sleeves",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "sleeves"]
            },
            "description": "Add a sleeve to a portfolio. All sleeve weights must sum to 1.0000. When sleeves are defined, rebalancing operates per-sleeve."
          }
        },
        {
          "name": "List Sleeves",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/sleeves",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "sleeves"]
            }
          }
        },
        {
          "name": "Get Sleeve",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/sleeves/{{sleeveId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "sleeves", "{{sleeveId}}"]
            }
          }
        },
        {
          "name": "Update Sleeve",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Core Equity (updated)\",\n  \"target_weight\": \"0.5500\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/sleeves/{{sleeveId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "sleeves", "{{sleeveId}}"]
            }
          }
        },
        {
          "name": "Remove Sleeve",
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/sleeves/{{sleeveId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "sleeves", "{{sleeveId}}"]
            }
          }
        }
      ]
    },
    {
      "name": "Rebalancing",
      "item": [
        {
          "name": "Trigger Rebalancing Run",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 202) {",
                  "    const body = pm.response.json();",
                  "    pm.environment.set('runId', body.run_id);",
                  "    pm.test('run_id set', () => pm.expect(body.run_id).to.be.a('string'));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"portfolio_ids\": [\"{{portfolioId}}\"],\n  \"drift_threshold\": \"0.0500\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/rebalancing",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "rebalancing"]
            },
            "description": "Proposes orders to bring portfolios back to target allocations. Does NOT execute orders — call the approve endpoint to submit."
          }
        },
        {
          "name": "List Rebalancing Runs",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/rebalancing?portfolio_id={{portfolioId}}&limit=20",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "rebalancing"],
              "query": [
                { "key": "portfolio_id", "value": "{{portfolioId}}" },
                { "key": "limit", "value": "20" }
              ]
            }
          }
        },
        {
          "name": "Get Rebalancing Run",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/rebalancing/{{runId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "rebalancing", "{{runId}}"]
            },
            "description": "Get rebalancing run with all proposed orders. Status: PENDING | ANALYZING | ORDERS_GENERATED | ORDERS_SUBMITTED | COMPLETED | FAILED."
          }
        },
        {
          "name": "Approve Rebalancing Run",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/rebalancing/{{runId}}/approve",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "rebalancing", "{{runId}}", "approve"]
            },
            "description": "Approve proposed orders and submit for execution. Returns 409 if run is not in ORDERS_GENERATED status."
          }
        }
      ]
    },
    {
      "name": "Performance",
      "item": [
        {
          "name": "Get Performance Metrics",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/performance?period=YTD",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "performance"],
              "query": [
                {
                  "key": "period",
                  "value": "YTD",
                  "description": "1D | 1W | 1M | 3M | YTD | 1Y | 3Y | SI"
                }
              ]
            },
            "description": "TWR uses Modified Dietz for sub-periods, chain-linked (GIPS-compliant). MWR is IRR. All return values are 10dp strings."
          }
        },
        {
          "name": "Get Performance Time Series",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/portfolios/{{portfolioId}}/performance/timeseries?from_date=2026-01-01&to_date=2026-06-27&granularity=daily",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "portfolios", "{{portfolioId}}", "performance", "timeseries"],
              "query": [
                { "key": "from_date", "value": "2026-01-01" },
                { "key": "to_date", "value": "2026-06-27" },
                { "key": "granularity", "value": "daily", "description": "daily | weekly | monthly" }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Billing",
      "item": [
        {
          "name": "Create Fee Schedule",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"portfolio_id\": \"{{portfolioId}}\",\n  \"fee_rate\": \"0.0400\",\n  \"frequency\": \"QUARTERLY\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/billing/schedules",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "billing", "schedules"]
            },
            "description": "Create an AUM fee schedule. fee_rate \"0.0400\" = 4 basis points/year (Interpose list rate)."
          }
        },
        {
          "name": "List Fee Schedules",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/billing/schedules?portfolio_id={{portfolioId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "billing", "schedules"],
              "query": [{ "key": "portfolio_id", "value": "{{portfolioId}}" }]
            }
          }
        },
        {
          "name": "Run Billing",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"period_end\": \"2026-06-30\",\n  \"portfolio_ids\": [\"{{portfolioId}}\"]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/billing/runs",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "billing", "runs"]
            },
            "description": "Execute a billing run (idempotent). Generates statements for each portfolio in the list."
          }
        },
        {
          "name": "List Billing Statements",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/billing/statements?portfolio_id={{portfolioId}}&period_start=2026-04-01",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "billing", "statements"],
              "query": [
                { "key": "portfolio_id", "value": "{{portfolioId}}" },
                { "key": "period_start", "value": "2026-04-01" }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Stream (WebSocket)",
      "item": [
        {
          "name": "Real-Time Portfolio Event Stream",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/pm/stream",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "pm", "stream"]
            },
            "description": "WebSocket upgrade. Use Postman WebSocket tab or wscat.\n\nServer pushes:\n- `com.interpose.pm.portfolio.updated`\n- `com.interpose.pm.rebalancing.initiated`\n- `com.interpose.pm.rebalancing.completed`"
          }
        }
      ]
    }
  ]
}
