Skip to main content
Portfolio endpoints are authenticated and scoped to the calling user. Every route requires Authorization: Bearer sf_live_xxx. Use these when an external app or agent needs the same data exposed by sf portfolio.

Start here

All authenticated routes return 401 unauthorized without a valid Bearer key.

State

GET returns the singleton state row for the user, or null if the autopilot has never written one. PUT is a fire-and-forget upsert — the autopilot writes here on every tick. End-user code rarely needs to call it; if you do, send a partial body and the upsert sets lastTickAt = now().

Config

GET returns the config row, or platform defaults if the user hasn’t saved one yet. PUT is a partial upsert. Fields you omit are left unchanged on an existing row, or fall back to defaults on a fresh row. userId, createdAt, and schedule identifiers are server-managed and stripped from the body. When enabled flips false → true, the platform creates a managed cloud schedule using cronExpression (default 0 7,19 * * *, America/Los_Angeles). When cronExpression changes while enabled, the schedule is updated. When enabled flips true → false, the schedule is deactivated. Schedule lifecycle is best-effort; config saves still succeed if the scheduler is temporarily unavailable.

Ticks

A tick is one autopilot evaluation cycle — actions taken, risk gates evaluated, balances at the time, total tick duration, and a free-text handoff note for the next tick to read. When envelope=true:
Without envelope, the response is the raw array; the next cursor is returned in the x-next-cursor response header instead. GET /api/portfolio/ticks/{id} returns one row by id, or 404 not found if it doesn’t belong to the caller. POST /api/portfolio/ticks is the writer the cloud tick uses to log itself; external integrations rarely need it. Body is a tick row minus id and userId. Server stamps tickAt, attributes the trace, and returns { ok: true }.

Trades

A trade is one entry (and optionally one exit) for P&L attribution. The autopilot writes one row per fill and updates it on close. direction is one of buy_yes, buy_no. exitReason is one of take_profit, stop_loss, thesis_exit, settlement, manual, or null for open trades. POST /api/portfolio/trades is the writer the cloud tick uses; the body should match the trade row minus id, userId, createdAt. Server stamps openedAt = now() if not provided. Returns { ok: true }.

Ledger reads

The portfolio ledger is the canonical append-only event source for fills, settlements, cancellations, and agent decisions. Every read route returns a paginated envelope and accepts the same filter set: limit, since, until, cursor, eventType, venue, marketId, source, thesisId, confidence. Daily and grouped attribution accept from / to instead of since / until and add groupBy for grouped reads.
/ledger, /fills, /activity return SfPage<PortfolioLedgerEntry>. /positions returns SfPage<PortfolioPositionSnapshot> snapshot rows derived from the ledger. /attribution/daily and /attribution/grouped return materialized PnL/cash/fee/slippage/position deltas keyed by day, source, venue, market, thesis, strategy, view, and confidence. /risk returns a single composite snapshot: balance, exposure utilization, daily loss utilization, drawdown utilization, position utilization, stale-data flag, last reconcile status, and execution mode. confidence values: exact, low, unknown. unknown rows are always counted but never silently collapsed into a thesis or strategy — they are surfaced explicitly so attribution does not invent linkage that the ledger could not prove.
curl
These routes back the /dashboard2/portfolio cockpit and the SDK sf.portfolio.* resource tree. They are read-only and sideEffect: none.

Ledger imports

Authenticated import routes ingest historical venue rows into the portfolio ledger. They write user_write ledger events with attribution confidence unknown until thesis or strategy linkage is proven. Each route supports dryRun: true for validation-only runs and rejects payloads larger than 2000 rows per source.

Client-supplied Kalshi rows

Idempotency is enforced server-side via stable per-row keys derived from trade_id / order_id / event_ticker and timestamps. Replays do not double-count PnL, cash, fees, slippage, or position deltas. Order/cancel rows with no PnL/cash/position/fee/slippage measures are not materialized as zero-measure attribution noise.

Server-side Kalshi pull

The user must first connect a Kalshi BYOK keypair via POST /api/portfolio/secrets. The server then signs Kalshi user-API requests locally, pulls fills and settlements, and runs them through the same idempotent import path as the client-supplied route. Global KALSHI_API_KEY_ID / KALSHI_PRIVATE_KEY_PEM environment variables are not mutated.
This route also powers the hourly portfolio-venue-import-scheduled Trigger task, which enumerates enabled portfolio_config users and imports automatically.

Client-supplied Polymarket rows

Polymarket redeem rows do not invent realized PnL when cost basis is missing — they record cashDeltaCents and settlementRevenueCents only. Server-side scheduled Polymarket import is a follow-up; today this route requires the client to supply rows.

Response shape

/kalshi/pull adds fetched: { fills, settlements } reflecting how many rows the server actually pulled before normalization. errors lists per-row reasons ({ kind: "fill" | "settlement", index: number, reason: string }) for rows that failed validation. The remaining successful rows still insert.

Views

A view is a user-authored conviction or note that the LLM portfolio manager reads each tick. Views influence which ideas the manager prioritizes. GET returns the user’s views ordered by conviction DESC. POST creates a new view:
PUT updates by id; send id plus the fields to change. userId and createdAt are stripped before write. 404 not found if the row isn’t owned. Server stamps updatedAt. DELETE takes { id } in the body; returns { ok: true } or 404 not found.

Strategy

Persistent instructions and constraints for the autopilot — read on every tick. GET returns rows ordered by priority ASC. POST body:
PUT updates by id; send id plus fields. DELETE takes { id } in the body. Both return { ok: true } or 404.

Credential connection

Connects an encrypted Kalshi BYOK keypair so the cloud autopilot can submit orders on the user’s behalf. The private key is encrypted before durable storage, plaintext is never returned by the API, and rotation uses the same endpoint as initial connection. POST body:
There is no GET /api/portfolio/secrets — the API never returns plaintext or ciphertext over the wire. To rotate, POST again (upsert). To revoke, DELETE. DELETE returns { ok: true } or 404 no secrets found.

Run now

Runs a cloud autopilot tick now, in addition to the user’s configured schedule.

CLI equivalents

See CLI command reference — Portfolio for every flag.

See also

Portfolio autopilot

Conceptual overview, risk gates, agent loop.

Risk gates

The hard / soft gate model.

Trade intents

The execution gateway used by every tick that places orders.

Authentication

Bearer keys, BYOK, sandbox vs live.