Opens in 2d 14hChecking…

Developers

CoveredLoop HTTP API Reference

CoveredLoop exposes an HTTP API for member integrations (personal access tokens), uptime probes, authentication, mobile-store billing hooks, and scheduled operations. The member surface covers the dashboard summary bubbles, the Reports generator, watch lists, alerts, Profit & Loss, Metrics, portfolio, holdings, and trades. First-party product screens also run as authenticated TanStack Start server functions inside the web app.

Version 2026.08.30.2 · Updated August 30, 2026 · Base URL https://coveredloop.com

/developers
Endpoints
45
Sections
6
Content type
application/json

In this version

v2026.08.30.2 · August 30, 2026. Same list is in the branded PDF.

  • Account-scoped personal access tokens (clp_…), read-only
  • Dashboard summary bubbles — full grant and per account
  • Reports generator — JSON, PDF, Excel, and Word (same as /app/reports)
  • Watch lists, price alerts, and trade-alert preferences
  • Profit & Loss calendar (day / month / opened) and Metrics groups
  • Portfolio, holdings, and trades scoped to the token grant

Conventions

Rules every integration should follow.

  • Send JSON bodies as application/json. Successful responses are JSON objects.
  • Errors use { "error": "message" } and an HTTP 4xx/5xx status.
  • Authorization for server-to-server calls is Authorization: Bearer <secret>. Never put secrets in query strings in production.
  • Members create a personal access token in Settings → API and send Authorization: Bearer clp_…. The token acts as that member (read-only). Optionally pin the token to specific accounts so a third-party app cannot see the rest of the book. GET /api/v1/dashboard, /reports, /portfolio, /pl/*, and /metrics honor that pin. Watch lists and price alerts are member-level.
  • Browser sign-in uses same-origin cookies on /api/auth/*. Preview / native clients may attach the Better Auth bearer token instead.
  • Rate limits apply to auth and admin mutations. Retry 429s with backoff.
  • All timestamps are ISO-8601 UTC unless noted.

Secrets

Set these in the host environment. Values are never returned by the API.

VariableUsed by
CRON_SECRETAll /api/cron/* routes
HEALTH_DETAIL_SECRETGET /api/health?detail=1 (required; not CRON_SECRET)
STORE_WEBHOOK_SECRETPOST /api/billing/store-notify (required; not CRON_SECRET)
DEV_VERIFY_SECRETPOST /api/dev/verify-email (required; localhost only)

Health & uptime

Lightweight probe for external monitors (Better Stack, UptimeRobot). Keyword match on "ok":true or "status":"up". Poll every 1–5 minutes.

GET

Public health probe

/api/health

Public

Returns a stable, secret-free payload for uptime tools. HTTP 200 when the database answers; 503 when it does not.

Auth: None

Success
{ "ok": true, "status": "up", "time": "2026-08-17T13:20:00.000Z" }
Errors

503 { ok: false, status: "down", time } when the database ping fails.

curl
curl -sS https://coveredloop.com/api/health
JavaScript
const res = await fetch("https://coveredloop.com/api/health");
const body = await res.json();
if (!body.ok) throw new Error("CoveredLoop is down");
Python
import requests
r = requests.get("https://coveredloop.com/api/health", timeout=10)
r.raise_for_status()
print(r.json())  # {'ok': True, 'status': 'up', 'time': '...'}
GET

Detailed health (authorized)

/api/health?detail=1

Operations

Adds host, database mode, and whether auth / Resend / Square / Upstash secrets are present. Never exposes secret values.

Auth: Authorization: Bearer $HEALTH_DETAIL_SECRET

NameInRequiredTypeDescription
detailqueryYes1 | trueRequest the authorized diagnostic payload.
AuthorizationheaderYesBearer <secret>Must match HEALTH_DETAIL_SECRET (not CRON_SECRET).
Success
{ "ok": true, "status": "up", "host": "coveredloop", "time": "...", "database": { "mode": "postgres", "hasDatabaseUrl": true, "ping": "ok:neon" }, "auth": { "betterAuthUrl": true, "betterAuthSecret": true }, "integrations": { "resend": true, "square": true, "upstash": true, "cronSecret": true } }
Errors

Without a valid bearer the public { ok, status, time } payload is returned instead (no 401).

curl
curl -sS -H "Authorization: Bearer $HEALTH_DETAIL_SECRET" \
  "https://coveredloop.com/api/health?detail=1"
JavaScript
const res = await fetch("https://coveredloop.com/api/health?detail=1", {
  headers: { Authorization: `Bearer ${process.env.HEALTH_DETAIL_SECRET}` },
});
console.log(await res.json());
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/health",
    params={"detail": "1"},
    headers={"Authorization": f"Bearer {os.environ['HEALTH_DETAIL_SECRET']}"},
    timeout=10,
)
print(r.json())

Authentication

Member accounts run through Better Auth at /api/auth/*. Browser clients use same-origin session cookies. Native or preview clients may send Authorization: Bearer <session token>. Email must be verified before full product access. MFA (TOTP) is supported.

POST

Create account

/api/auth/sign-up/email

Member session

Registers an email/password member. A verification email is sent. Session may be created pending verification.

Auth: None (public signup)

NameInRequiredTypeDescription
emailbodyYesstringMember email.
passwordbodyYesstringAccount password.
namebodyYesstringDisplay name.
callbackURLbodyNostringWhere to send the user after verification.
Success
{ "user": { "id": "...", "email": "...", "name": "..." }, "token": "..." }
Errors

400 if email is taken or the password is too short; 429 if rate-limited.

curl
curl -sS -X POST https://coveredloop.com/api/auth/sign-up/email \
  -H "Content-Type: application/json" \
  -d '{"email":"trader@example.com","password":"••••••••","name":"Alex Trader"}'
JavaScript
const res = await fetch("https://coveredloop.com/api/auth/sign-up/email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  credentials: "include",
  body: JSON.stringify({
    email: "trader@example.com",
    password: "••••••••",
    name: "Alex Trader",
    callbackURL: "/app",
  }),
});
Python
import requests
r = requests.post(
    "https://coveredloop.com/api/auth/sign-up/email",
    json={"email": "trader@example.com", "password": "••••••••", "name": "Alex Trader"},
    timeout=15,
)
print(r.status_code, r.json())
POST

Sign in with email

/api/auth/sign-in/email

Member session

Creates a session cookie. If MFA is enabled the payload includes twoFactorRedirect: true and the client must complete /api/auth/two-factor/verify-totp.

Auth: None

NameInRequiredTypeDescription
emailbodyYesstringAccount email.
passwordbodyYesstringAccount password.
callbackURLbodyNostringPost-login path for browser clients.
Success
{ "user": { "id": "...", "email": "..." }, "token": "..." }
// or { "twoFactorRedirect": true } when MFA is on
Errors

401 invalid credentials; 403 email not verified; 429 rate-limited.

curl
curl -sS -X POST https://coveredloop.com/api/auth/sign-in/email \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{"email":"trader@example.com","password":"••••••••"}'
JavaScript
const res = await fetch("https://coveredloop.com/api/auth/sign-in/email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  credentials: "include",
  body: JSON.stringify({ email: "trader@example.com", password: "••••••••" }),
});
const data = await res.json();
if (data.twoFactorRedirect) location.href = "/two-factor";
Python
import requests
s = requests.Session()
r = s.post(
    "https://coveredloop.com/api/auth/sign-in/email",
    json={"email": "trader@example.com", "password": "••••••••"},
    timeout=15,
)
print(r.json())
GET

Read current session

/api/auth/get-session

Member session

Returns the signed-in user or an empty session. Use cookies or a bearer token.

Auth: Session cookie or Authorization: Bearer <token>

NameInRequiredTypeDescription
AuthorizationheaderNoBearer <token>Optional. Preview / native clients send the Better Auth session token.
Success
{ "session": { "id": "...", "userId": "...", "expiresAt": "..." }, "user": { "id": "...", "email": "...", "name": "..." } }
Errors

200 with null session when signed out.

curl
curl -sS https://coveredloop.com/api/auth/get-session -b cookies.txt
JavaScript
const res = await fetch("https://coveredloop.com/api/auth/get-session", { credentials: "include" });
const { user } = await res.json();
Python
import requests
r = requests.get("https://coveredloop.com/api/auth/get-session", cookies=session_cookies, timeout=10)
print(r.json().get("user"))
POST

Sign out

/api/auth/sign-out

Member session

Invalidates the current session cookie / bearer token.

Auth: Session cookie or bearer token

Success
{ "success": true }
Errors

400 if no session is present.

curl
curl -sS -X POST https://coveredloop.com/api/auth/sign-out -b cookies.txt
JavaScript
await fetch("https://coveredloop.com/api/auth/sign-out", { method: "POST", credentials: "include" });
Python
requests.post("https://coveredloop.com/api/auth/sign-out", cookies=session_cookies, timeout=10)
POST

Request password reset

/api/auth/forget-password

Member session

Emails a one-time reset link via Resend. Always returns a generic success to avoid account enumeration.

Auth: None

NameInRequiredTypeDescription
emailbodyYesstringAccount email.
redirectTobodyNostringReset page, typically /reset-password.
Success
{ "status": true }
Errors

429 if reset emails are requested too often.

curl
curl -sS -X POST https://coveredloop.com/api/auth/forget-password \
  -H "Content-Type: application/json" \
  -d '{"email":"trader@example.com","redirectTo":"/reset-password"}'
JavaScript
await fetch("https://coveredloop.com/api/auth/forget-password", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "trader@example.com", redirectTo: "/reset-password" }),
});
Python
requests.post(
    "https://coveredloop.com/api/auth/forget-password",
    json={"email": "trader@example.com", "redirectTo": "/reset-password"},
    timeout=15,
)
POST

Complete password reset

/api/auth/reset-password

Member session

Sets a new password using the token from the reset email.

Auth: Reset token from email

NameInRequiredTypeDescription
tokenbodyYesstringToken from the reset link.
newPasswordbodyYesstringReplacement password.
Success
{ "status": true }
Errors

400 expired or invalid token.

curl
curl -sS -X POST https://coveredloop.com/api/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{"token":"<token>","newPassword":"••••••••"}'
JavaScript
await fetch("https://coveredloop.com/api/auth/reset-password", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token, newPassword }),
});
Python
requests.post(
    "https://coveredloop.com/api/auth/reset-password",
    json={"token": token, "newPassword": new_password},
    timeout=15,
)
GET/POST

Verify email

/api/auth/verify-email

Member session

Confirms the address using the token from the verification email. GET is used by the email link.

Auth: Verification token

NameInRequiredTypeDescription
tokenqueryYesstringToken from the verification email.
callbackURLqueryNostringBrowser redirect after success.
Success
302 to callbackURL, or JSON { status: true }.
Errors

400 invalid or expired token.

curl
curl -sSI "https://coveredloop.com/api/auth/verify-email?token=<token>&callbackURL=/app"
JavaScript
// Prefer the emailed link. To resend:
await fetch("https://coveredloop.com/api/auth/send-verification-email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  credentials: "include",
  body: JSON.stringify({ email: "trader@example.com", callbackURL: "/app" }),
});
Python
requests.get(
    "https://coveredloop.com/api/auth/verify-email",
    params={"token": token, "callbackURL": "/app"},
    allow_redirects=False,
    timeout=15,
)
POST

Verify MFA code

/api/auth/two-factor/verify-totp

Member session

Completes sign-in (or confirms enabling MFA) with a 6-digit authenticator code.

Auth: Pending MFA session cookie

NameInRequiredTypeDescription
codebodyYesstring6-digit TOTP from the authenticator app.
Success
{ "token": "...", "user": { ... } }
Errors

401 invalid code.

curl
curl -sS -X POST https://coveredloop.com/api/auth/two-factor/verify-totp \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"code":"123456"}'
JavaScript
await fetch("https://coveredloop.com/api/auth/two-factor/verify-totp", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  credentials: "include",
  body: JSON.stringify({ code: "123456" }),
});
Python
s.post("https://coveredloop.com/api/auth/two-factor/verify-totp", json={"code": "123456"}, timeout=10)

Member API (access tokens)

Machine-to-machine access for a member's own data. Create a token in Settings → API. Optionally limit the token to specific accounts (Tradier Live, Paper, Manual, or Sample). Send Authorization: Bearer clp_… on every request. Copy the full key anytime from the token list while signed in (encrypted at rest). Tokens are hashed for lookup and are read-only. They do not grant admin, another user's data, or accounts you did not pin. Existing tokens with no account list keep access to every current and future account. Pass ?account=<id> on book endpoints to further filter within the grant. GET /api/v1/dashboard returns the dashboard summary bubbles for the full grant and per account. GET /api/v1/reports generates the same PDF / Excel / Word / JSON report as the Reports page. Profit & Loss and Metrics match the in-app Calendar and Metrics pages (token-scoped accounts, not the dashboard book selector). Watch lists and price alerts are member-level (not brokerage-account rows); trade-alert preferences are filtered to the token's granted accounts.

GET

Current member

/api/v1/me

Member session

Returns the account that owns the access token, a compact membership snapshot, and which brokerage accounts this token may read.

Auth: Authorization: Bearer clp_… (Settings → API)

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
Success
{ "id": "usr_…", "email": "trader@example.com", "name": "Alex", "membership": { "levelId": "free", "liveAccess": false, "frozen": false }, "accountScope": { "all": false, "accountIds": ["6YA12345"] } }
Errors

401 invalid / revoked / expired token; 403 banned account; 429 rate limited.

curl
curl -sS https://coveredloop.com/api/v1/me \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
console.log(await res.json());
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=15,
)
print(r.json())
GET

Membership status

/api/v1/membership

Member session

Plan, live-access flag, freeze, and renewal dates for the token owner.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token.
Success
{ "status": "active", "planId": "annual", "liveAccess": true, "frozen": false, "nextRenewalAt": "2027-08-17T00:00:00.000Z" }
Errors

401 / 403 / 429 as on /api/v1/me.

curl
curl -sS https://coveredloop.com/api/v1/membership \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/membership", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/membership",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=15,
)
GET

Portfolio snapshot

/api/v1/portfolio

Member session

Active data mode, selected accounts, summary, returns, holdings, trades, covered calls, and credit spreads. Scoped to the token's granted accounts (or the dashboard book if the token is unrestricted). Other accounts are omitted.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
Success
{ "mode": "live", "summary": { "totalEquity": 125000, "cashBalance": 8200 }, "accounts": [], "holdings": [], "trades": [], "scope": { "all": false, "accountIds": ["6YA12345"] } }
Errors

401 / 403 / 429. Live mode requires a paid plan, same as the app.

curl
curl -sS "https://coveredloop.com/api/v1/portfolio?account=6YA12345" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/portfolio", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const book = await res.json();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/portfolio",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=30,
)
print(r.json()["summary"])
GET

Dashboard summary bubbles

/api/v1/dashboard

Member session

Every summary tile on /app: equity / cash / buying potential / encumbered cash / open investments / Day P&L / overnight / overnight+session / trading costs / open positions / net transfers, the chart-range P&L row (realized, unrealized, current unrealized, wheel premium, credit-spread premium), and Bal + TWR returns. Top-level objects are the combined granted book. accounts[] repeats the summary and P&L tiles per brokerage account. Pass ?account= to load one account only (combined then matches that row). P&L tiles follow ?range= (same windows as the dashboard chart; default 1D). Tokens stay read-only.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
rangequeryNo1D | 1W | 1M | 3M | 6M | YTD | 1Y | ALLWindow for the P&L bubbles (realized / unrealized / premiums). Default 1D, matching the dashboard chart. Session tiles (Day P&L, overnight) are always today and ignore this.
Success
{ "mode": "live", "range": "1D", "summary": [{ "id": "total_equity", "label": "Total equity", "value": 125000, "unit": "usd" }], "pl": [{ "id": "realizedPl", "value": 210.5, "unit": "usd" }], "returns": [{ "id": "bal_daily", "value": 0.42, "unit": "pct" }], "accounts": [{ "id": "6YA12345", "label": "IRA", "summary": [], "pl": [] }] }
Errors

401 / 403 / 404 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/dashboard?account=6YA12345&range=1M" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/dashboard?range=1M", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const dash = await res.json();
const equity = dash.summary.find((b) => b.id === "total_equity");
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/dashboard",
    params={"range": "1M"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=30,
)
book = r.json()
print(book["summary"])
for acct in book["accounts"]:
    print(acct["id"], acct["summary"])
GET

Generate a report

/api/v1/reports

Member session

Same generator as Reports in the app. Default format=json returns the executive summary, score, metrics, and optional trade list. Pass format=pdf|xlsx|docx to download the branded file (PDF/Word skip in-app charts because those need a browser canvas; Excel is complete). Filters match the Reports page: date range, status, symbols, trade types, unrealized, min P&L. Token-scoped. Pass ?account= to generate for one granted account, or ?accounts=id1,id2 to further limit within the grant.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
formatqueryNojson | pdf | xlsx | docxjson (default) for a payload; pdf / xlsx / docx for a file download.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
statusqueryNoopen | closedComma-separated. Default both.
symbolqueryNostringComma-separated symbols / underlyings. Empty = all.
typequeryNostock | etf | covered_call | credit_spread | option | otherComma-separated trade types. Default all.
accountsqueryNostringComma-separated account ids within the token grant. Alias: accountIds.
unrealizedqueryNo0 | 1Include open-position unrealized in totals and the min-P&L filter. Default 1.
minAbsPlqueryNonumberDrop trades whose absolute P&L is below this amount.
detailsqueryNo0 | 1JSON only: include the compact trade list. Default off.
Success
{ "title": "CoveredLoop Portfolio Report · All dates", "summary": { "tradeCount": 42, "realizedPl": 4210.5, "totalPl": 5090.5 }, "overallScore": 72.4, "filename": "CoveredLoop-Report-2026-08-30.json" }
Errors

400 unknown format; 401 / 403 / 404 / 429. File generation is limited to 12 per minute.

curl
curl -sS "https://coveredloop.com/api/v1/reports?format=xlsx&from=2026-01-01" \
  -H "Authorization: Bearer clp_…" \
  -o CoveredLoop-Report.xlsx
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/reports?format=pdf&from=2026-01-01", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const blob = await res.blob();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/reports",
    params={"format": "xlsx", "from": "2026-01-01", "status": "closed"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=60,
)
open("CoveredLoop-Report.xlsx", "wb").write(r.content)
GET

Accounts

/api/v1/accounts

Member session

Broker / manual / sample accounts this token may read. Ungranted accounts are not listed (no labels or equity).

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token.
accountqueryNostringReturn only this granted account id. Alias: accountId.
Success
{ "mode": "live", "selectedAccountIds": ["6YA12345"], "accounts": [{ "id": "6YA12345", "label": "IRA" }], "scope": { "all": false, "accountIds": ["6YA12345"] } }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/accounts \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/accounts", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/accounts",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Holdings

/api/v1/holdings

Member session

Positions in the accounts this token may read (symbol, quantity, cost, market value, P&L).

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token.
accountqueryNostringLimit to one granted account id. Alias: accountId.
Success
{ "mode": "live", "count": 4, "holdings": [{ "symbol": "AAPL", "quantity": 100 }], "scope": { "all": false, "accountIds": ["6YA12345"] } }
Errors

401 / 403 / 404 / 429.

curl
curl -sS https://coveredloop.com/api/v1/holdings \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/holdings", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/holdings",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Trades

/api/v1/trades

Member session

Trade blotter for granted accounts. Filter by status, symbol, and account; paginate with limit/offset.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token.
accountqueryNostringLimit to one granted account id. Alias: accountId.
statusqueryNoopen | closedFilter by open or closed trades.
symbolqueryNostringFilter by underlying or trade symbol.
limitqueryNointeger (1–500, default 100)Page size.
offsetqueryNointeger (default 0)Rows to skip.
Success
{ "mode": "manual", "total": 12, "limit": 100, "offset": 0, "trades": [] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/trades?status=open&limit=50" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/trades?status=open", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/trades",
    params={"status": "open", "limit": 50},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Watch lists

/api/v1/watchlists

Member session

Research watch lists for the token owner (id, name, symbol count, symbols). Same lists as Research → Watch lists and Alerts. Member-level — not filtered by the token's brokerage account pin.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
Success
{ "count": 2, "watchlists": [{ "id": "wl-…", "name": "Tech", "symbolCount": 3, "symbols": ["AAPL", "MSFT", "NVDA"], "createdAt": "2026-08-12T14:02:00.000Z", "updatedAt": "2026-08-28T16:10:00.000Z" }] }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/watchlists \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/watchlists", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const { watchlists } = await res.json();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/watchlists",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
print(r.json()["count"])
GET

One watch list

/api/v1/watchlists/{id}

Member session

Watch list detail with each symbol's last price, session change, quote source (live / base / unknown), color tag, and when it was added.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
idpathYesstringWatch list id from GET /api/v1/watchlists (wl-…).
Success
{ "watchlist": { "id": "wl-…", "name": "Tech", "symbolCount": 1, "symbols": ["AAPL"], "items": [{ "symbol": "AAPL", "name": "Apple Inc.", "price": 227.4, "changePct": 0.82, "change": 1.85, "quoteSource": "live", "addedAt": "2026-08-12T14:02:00.000Z", "colorTag": "green" }] } }
Errors

400 missing id; 404 not found or not yours; 401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/watchlists/wl-… \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch(`https://coveredloop.com/api/v1/watchlists/${listId}`, {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const { watchlist } = await res.json();
Python
import os, requests
r = requests.get(
    f"https://coveredloop.com/api/v1/watchlists/{list_id}",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
print(r.json()["watchlist"]["items"])
GET

Price alerts

/api/v1/alerts

Member session

Stock, ETF, and option price alerts for the token owner (same rows as /app/alerts). Filter by status, symbol, or unread triggered notices. Member-level — not filtered by brokerage account pin.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
statusqueryNoactive | triggered | dismissedFilter by alert status.
symbolqueryNostringUnderlying or OCC option symbol (case-insensitive).
unreadqueryNo0 | 1If 1, only triggered alerts that have not been dismissed/seen in-app.
Success
{ "total": 1, "alerts": [{ "id": "pa_…", "symbol": "AAPL", "instrument": "stock", "targetPrice": 230, "direction": "above", "status": "active", "lastPrice": 227.4, "triggeredPrice": null, "triggeredAt": null, "seenAt": null, "note": "", "createdAt": "2026-08-20T13:00:00.000Z", "updatedAt": "2026-08-28T16:10:00.000Z" }] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/alerts?status=active" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/alerts?status=active", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const { alerts } = await res.json();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/alerts",
    params={"status": "active"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
print(r.json()["total"])
GET

One price alert

/api/v1/alerts/{id}

Member session

A single price alert owned by the token member.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
idpathYesstringPrice alert id from GET /api/v1/alerts (pa_…).
Success
{ "alert": { "id": "pa_…", "symbol": "AAPL", "targetPrice": 230, "direction": "above", "status": "active" } }
Errors

400 missing id; 404 not found or not yours; 401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/alerts/pa_… \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch(`https://coveredloop.com/api/v1/alerts/${alertId}`, {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    f"https://coveredloop.com/api/v1/alerts/{alert_id}",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=15,
)
GET

Trade alert preferences

/api/v1/alerts/trade

Member session

Email preferences for option expiration, ITM/OTM crosses, and uncovered-share notices (Settings → Alerts). accountIds is intersected with the token grant; an empty stored list means every granted account.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
Success
{ "prefs": { "enabled": true, "expirationAlerts": true, "daysBeforeExpiration": 7, "alsoOnExpirationDay": true, "itmAlerts": true, "otmAlerts": false, "uncoveredAlerts": true, "includeCoveredCalls": true, "includeCreditSpreads": true, "includeSampleData": false, "accountIds": ["6YA12345"], "includeManual": true, "includeTradier": true }, "scope": { "all": false, "accountIds": ["6YA12345"] } }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/alerts/trade \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/alerts/trade", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const { prefs } = await res.json();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/alerts/trade",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=15,
)
print(r.json()["prefs"]["enabled"])
GET

Calendar P&L (by day)

/api/v1/pl/calendar

Member session

Same figures as Profit and Loss → Calendar. Each day includes realized P&L on the close date, unrealized level and day change, trades opened that day, and how many positions were still open. Compact trade lists are included unless details=0. Pass positions=1 to attach still-open mark-to-market rows on every day (large). Realized is close-to-close; overnight is a slice of that, not a second total.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
detailsqueryNo0 | 1Include compact trade lists. Calendar and opened default on; months, summary, and distribution default off.
positionsqueryNo0 | 1Include still-open positions with unrealized level/change on each day. Default off — use /api/v1/pl/day for one date.
Success
{ "start": "2026-01-01", "end": "2026-08-30", "totals": { "realized": 4210.5, "unrealized": 880.0 }, "days": [{ "date": "2026-08-28", "realized": 210.0, "unrealizedChange": -12.5, "openedCount": 2, "stillOpenCount": 8 }] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/calendar?from=2026-08-01&to=2026-08-30" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/calendar?from=2026-08-01", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
const cal = await res.json();
Python
import os, requests
r = requests.get(
    "https://coveredloop.com/api/v1/pl/calendar",
    params={"from": "2026-08-01", "to": "2026-08-30"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=30,
)
print(r.json()["totals"])
GET

One calendar day

/api/v1/pl/day

Member session

Full breakdown for a single YYYY-MM-DD: realized closes, trades opened that day, and every position that was still open with unrealized level and change vs the prior day.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
datequeryYesYYYY-MM-DDCalendar day. Alias: day.
Success
{ "date": "2026-08-28", "realized": 210.0, "realizedTrades": [], "opened": [], "positions": [{ "symbol": "AAPL", "unrealizedChange": -12.5 }] }
Errors

400 if date is missing or not YYYY-MM-DD; 401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/day?date=2026-08-28" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/day?date=2026-08-28", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/day",
    params={"date": "2026-08-28"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Monthly P&L summaries

/api/v1/pl/months

Member session

One row per YYYY-MM: realized P&L, win/loss counts, trades opened that month, unrealized level at month-end and change during the month, plus long vs short slices. Pass details=1 for compact closed trades.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
detailsqueryNo0 | 1Include compact trade lists. Calendar and opened default on; months, summary, and distribution default off.
Success
{ "months": [{ "month": "2026-08", "realized": 1840.0, "unrealizedChange": 120.5, "openedCount": 14, "long": { "realized": 900.0 }, "short": { "realized": 940.0 } }] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/months?from=2026-01-01" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/months?from=2026-01-01", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/months",
    params={"from": "2026-01-01"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=30,
)
GET

Opened trades by day

/api/v1/pl/opened

Member session

Structures opened on each day in the range (covered-call / spread legs collapsed the same way as the Calendar opened overlay).

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
Success
{ "total": 9, "days": [{ "date": "2026-08-28", "count": 2, "groups": [{ "symbol": "AAPL", "strategy": "covered_call", "stillOpen": true }] }] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/opened?from=2026-08-01" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/opened?from=2026-08-01", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/opened",
    params={"from": "2026-08-01"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Period P&L summary

/api/v1/pl/summary

Member session

Long vs short realized and unrealized for a date range — the Summary tab in Profit and Loss.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
detailsqueryNo0 | 1Include compact trade lists. Calendar and opened default on; months, summary, and distribution default off.
Success
{ "start": "2026-01-01", "end": "2026-08-30", "totalRealized": 4210.5, "totalUnrealized": 880.0, "long": { "realized": 3000.0, "unrealized": 400.0 }, "short": { "realized": 1210.5, "unrealized": 480.0 } }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/summary?from=2026-01-01" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/summary?from=2026-01-01", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/summary",
    params={"from": "2026-01-01"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

P&L distribution

/api/v1/pl/distribution

Member session

Closed-trade P&L buckets matching Profit and Loss → Distribution. Dimension: symbol, strategy, sector, weekday, account, month, or year.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
detailsqueryNo0 | 1Include compact trade lists. Calendar and opened default on; months, summary, and distribution default off.
dimensionqueryNosymbol | strategy | sector | weekday | account | month | yearBucket key. Default symbol.
Success
{ "dimension": "symbol", "buckets": [{ "key": "AAPL", "label": "AAPL", "pl": 420.0, "tradeCount": 6, "winCount": 4, "lossCount": 2 }] }
Errors

400 unknown dimension; 401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/distribution?dimension=strategy" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/distribution?dimension=strategy", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/distribution",
    params={"dimension": "strategy"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

P&L chart series

/api/v1/pl/series

Member session

Realized bars and unrealized level/change for the Chart tab. Grain: day, week, month (default), or year.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
fromqueryNoYYYY-MM-DDRange start (inclusive). Defaults to the first trade (capped at about four years).
toqueryNoYYYY-MM-DDRange end (inclusive). Defaults to today.
grainqueryNoday | week | month | yearBucket size. Default month.
Success
{ "grain": "month", "realized": [{ "key": "2026-08", "total": 1840.0 }], "unrealized": [{ "key": "2026-08", "level": 880.0, "change": 120.5 }] }
Errors

401 / 403 / 429.

curl
curl -sS "https://coveredloop.com/api/v1/pl/series?grain=month" \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/pl/series?grain=month", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/pl/series",
    params={"grain": "month"},
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

All metrics

/api/v1/metrics

Member session

Every Metrics-page number: summary tiles plus trade, quality, and risk groups. Same formulas as /app/metrics for the token's accounts.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
Success
{ "summary": { "closedTrades": 42, "winners": 28, "losers": 14, "netEquityPl": 4210.5, "maxDrawdownPct": 8.2 }, "trade": [], "quality": [], "risk": [] }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/metrics \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/metrics", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/metrics",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=30,
)
GET

Metrics summary

/api/v1/metrics/summary

Member session

Closed trades, winners/losers, win rate, net equity P&L, and max drawdown (peak/trough dates and dollars).

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
Success
{ "closedTrades": 42, "winners": 28, "losers": 14, "winRate": 0.667, "netEquityPl": 4210.5, "maxDrawdownPct": 8.2, "drawdown": { "peakDate": "2026-03-12", "troughDate": "2026-04-02" } }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/metrics/summary \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/metrics/summary", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/metrics/summary",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Trade metrics

/api/v1/metrics/trade

Member session

Win/loss ratio, average trade P&L, profit factor, average P&L %, winning/losing %, and trade counts.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
Success
{ "group": "trade", "items": [{ "key": "profitFactor", "label": "Profit Factor", "value": 1.84, "format": "ratio" }] }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/metrics/trade \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/metrics/trade", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/metrics/trade",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Quality metrics

/api/v1/metrics/quality

Member session

System Quality Number and trade expectancy.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
Success
{ "group": "quality", "items": [{ "key": "systemQualityNumber", "label": "System Quality Number", "value": 1.9, "format": "sqn" }] }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/metrics/quality \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/metrics/quality", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/metrics/quality",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)
GET

Risk metrics

/api/v1/metrics/risk

Member session

Gain-to-pain, Calmar, Sharpe, Omega, ulcer performance index, recovery factor, and tail ratio.

Auth: Authorization: Bearer clp_…

NameInRequiredTypeDescription
AuthorizationheaderYesBearer clp_…Personal access token generated in Settings → API.
accountqueryNostringFurther limit this request to one granted account id. Alias: accountId. 404 if the id is not in your book; 403 if the token is not allowed to read it.
bookqueryNosample | manual | live | paperLimit to one Profit & Loss book within the token grant.
Success
{ "group": "risk", "items": [{ "key": "sharpeRatio", "label": "Sharpe Ratio", "value": 1.12, "format": "ratio" }] }
Errors

401 / 403 / 429.

curl
curl -sS https://coveredloop.com/api/v1/metrics/risk \
  -H "Authorization: Bearer clp_…"
JavaScript
const res = await fetch("https://coveredloop.com/api/v1/metrics/risk", {
  headers: { Authorization: `Bearer ${process.env.COVEREDLOOP_TOKEN}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/v1/metrics/risk",
    headers={"Authorization": f"Bearer {os.environ['COVEREDLOOP_TOKEN']}"},
    timeout=20,
)

Store billing hooks

Android (Google Play) and iOS (App Store) notify CoveredLoop of subscription events through a normalized webhook. Website Square checkout does not use this route. Native apps or a thin adapter POST after verifying the store receipt.

POST

Record a store subscription event

/api/billing/store-notify

Store / partner

Writes a ledger row and updates membership (purchase, renewal, refund, cancel, freeze, unfreeze, expire, grant). Duplicate storeTxId + kind combinations are ignored.

Auth: Authorization: Bearer $STORE_WEBHOOK_SECRET plus HMAC headers

NameInRequiredTypeDescription
AuthorizationheaderYesBearer <secret>STORE_WEBHOOK_SECRET. Must not be CRON_SECRET.
X-CoveredLoop-TimestampheaderYesunix secondsRequest time. Rejected if skew > 5 minutes.
X-CoveredLoop-SignatureheaderYessha256=<hex>HMAC-SHA256 of timestamp + '.' + raw JSON body using STORE_WEBHOOK_SECRET.
channelbodyYes"android" | "ios"Must not be website.
kindbodyYespurchase | renewal | refund | cancel | freeze | unfreeze | expire | grantNormalized store event.
userIdbodyYesstringCoveredLoop user id.
storeTxIdbodyYesstringStore transaction id (idempotency key with kind).
originalTxIdbodyNostringOriginal subscription transaction. Defaults to storeTxId.
planIdbodyNomonthly | annual | lifetimeMembership plan.
amountCentsbodyNointegerGross amount in USD cents. Refunds are stored as negative.
Success
{ "ok": true, "duplicate": false }
Errors

401 unauthorized or missing HMAC; 400 invalid channel/kind/userId/storeTxId or unknown user; 503 if STORE_WEBHOOK_SECRET is unset or equals CRON_SECRET.

curl
TS=$(date +%s)
BODY='{"channel":"ios","kind":"purchase","userId":"usr_...","planId":"annual","storeTxId":"1000000123456789","amountCents":19900}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$STORE_WEBHOOK_SECRET" -hex | awk '{print $NF}')
curl -sS -X POST https://coveredloop.com/api/billing/store-notify \
  -H "Authorization: Bearer $STORE_WEBHOOK_SECRET" \
  -H "X-CoveredLoop-Timestamp: $TS" \
  -H "X-CoveredLoop-Signature: sha256=$SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"
JavaScript
import { createHmac } from "node:crypto";
const body = JSON.stringify({
  channel: "android",
  kind: "renewal",
  userId,
  planId: "monthly",
  storeTxId,
  amountCents: 2900,
});
const ts = Math.floor(Date.now() / 1000);
const sig = createHmac("sha256", process.env.STORE_WEBHOOK_SECRET)
  .update(`${ts}.${body}`)
  .digest("hex");
await fetch("https://coveredloop.com/api/billing/store-notify", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STORE_WEBHOOK_SECRET}`,
    "X-CoveredLoop-Timestamp": String(ts),
    "X-CoveredLoop-Signature": `sha256=${sig}`,
    "Content-Type": "application/json",
  },
  body,
});
Python
import os, requests
r = requests.post(
    "https://coveredloop.com/api/billing/store-notify",
    headers={"Authorization": f"Bearer {os.environ['STORE_WEBHOOK_SECRET']}"},
    json={
        "channel": "ios",
        "kind": "purchase",
        "userId": user_id,
        "planId": "annual",
        "storeTxId": store_tx_id,
        "amountCents": 19900,
    },
    timeout=15,
)
r.raise_for_status()

Scheduled operations

Host schedulers (Vercel Cron) call these routes. Production accepts Authorization: Bearer $CRON_SECRET only. Local/dev may use ?secret= for tools. Do not expose these to third-party apps.

GET/POST

Deep health watch

/api/cron/health-watch

Operations

Evaluates database and integration config. Emails ops on degraded/down. Optional daily all-clear.

Auth: Authorization: Bearer $CRON_SECRET

NameInRequiredTypeDescription
heartbeatqueryNo1 | trueAlso email when healthy (daily all-clear).
notifyqueryNo0 | falseSet to 0 to evaluate without sending email.
Success
{ "ok": true, "status": "up", "time": "...", "issues": [], "emailed": false, "heartbeat": false }
Errors

401 unauthorized; 503 if CRON_SECRET is unset in production; 500 on evaluation failure.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  "https://coveredloop.com/api/cron/health-watch?heartbeat=1"
JavaScript
await fetch("https://coveredloop.com/api/cron/health-watch?heartbeat=1", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/health-watch",
    params={"heartbeat": "1"},
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=30,
)
GET/POST

Snapshot emails & trade alerts

/api/cron/email-reports

Operations

Sends due portfolio snapshot emails and processes trade-event alerts. Typical schedule: 21:00 UTC daily.

Auth: Authorization: Bearer $CRON_SECRET

NameInRequiredTypeDescription
forcequeryNoday | week | month | quarter | yearForce a snapshot frequency for all due reports.
Success
{ "ok": true, "snapshots": { ... }, "tradeAlerts": { ... } }
Errors

401 unauthorized; 500 if the mail job fails.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  "https://coveredloop.com/api/cron/email-reports"
JavaScript
await fetch("https://coveredloop.com/api/cron/email-reports", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/email-reports",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=60,
)
GET/POST

Membership renewals

/api/cron/membership-renewals

Operations

Applies scheduled freezes, auto-unfreezes, cancels, and Square card-on-file renewals.

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "renewed": 0, "canceled": 0, "frozen": 0, "unfrozen": 0 }
Errors

401 unauthorized; 500 if Square renewal fails.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/membership-renewals
JavaScript
await fetch("https://coveredloop.com/api/cron/membership-renewals", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/membership-renewals",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=60,
)
GET/POST

Research quotes & price alerts

/api/cron/market-quotes

Operations

During regular hours: incremental quote + technicals rotation. After the close: overnight level-set. Also syncs the listed universe and fires due price alerts.

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "mode": "level-set" | undefined, "listing": { ... }, "priceAlerts": { ... } }
Errors

401 unauthorized; 500 if quote refresh fails.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/market-quotes
JavaScript
await fetch("https://coveredloop.com/api/cron/market-quotes", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/market-quotes",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=60,
)
GET/POST

Sample portfolio roll

/api/cron/sample-roll

Operations

Daily Sample Portfolio expiration / recommended-cover job after the cash session (21:20 UTC).

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "rolled": true, "reason": "cron" }
Errors

401 unauthorized; 500 on roll failure.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/sample-roll
JavaScript
await fetch("https://coveredloop.com/api/cron/sample-roll", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/sample-roll",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=30,
)
GET/POST

Store settlement rollup

/api/cron/store-settlements

Operations

Monthly rollup of Website / Android / iOS subscription settlements for Apple and Google filing audit.

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "count": 3, "periods": [{ "channel": "ios", "start": "...", "end": "...", "status": "ready", "netCents": 0 }] }
Errors

401 unauthorized; 500 on rollup failure.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/store-settlements
JavaScript
await fetch("https://coveredloop.com/api/cron/store-settlements", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/store-settlements",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=30,
)
GET/POST

Audit log retention

/api/cron/audit-retention

Operations

Purges expired audit rows by age and optional max-row cap. Suggested 03:15 UTC daily.

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "deleted": 12, "byLevel": { ... } }
Errors

401 unauthorized; 500 on purge failure.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/audit-retention
JavaScript
await fetch("https://coveredloop.com/api/cron/audit-retention", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/audit-retention",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=30,
)
GET/POST

Knowledge-base pending reminder

/api/cron/kb-pending-reminder

Operations

Weekly email to admins when Help knowledge-base questions are waiting for review. No email if the queue is empty. Suggested Monday 13:00 UTC.

Auth: Authorization: Bearer $CRON_SECRET

Success
{ "ok": true, "pending": 3, "emailed": true, "skipped": false }
Errors

401 unauthorized; 500 if the reminder job fails.

curl
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
  https://coveredloop.com/api/cron/kb-pending-reminder
JavaScript
await fetch("https://coveredloop.com/api/cron/kb-pending-reminder", {
  headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` },
});
Python
import os, requests
requests.get(
    "https://coveredloop.com/api/cron/kb-pending-reminder",
    headers={"Authorization": f"Bearer {os.environ['CRON_SECRET']}"},
    timeout=30,
)

Local development

Disabled on production, Vercel preview, and any non-loopback host. Do not call from third-party apps.

POST

Mark email verified (localhost)

/api/dev/verify-email

Local only

Lets capture scripts sign in without Resend. Returns 404 everywhere except 127.0.0.1 / localhost.

Auth: Authorization: Bearer $DEV_VERIFY_SECRET (required)

NameInRequiredTypeDescription
emailbodyYesstringLocal account to mark verified.
AuthorizationheaderYesBearer <secret>DEV_VERIFY_SECRET. 404 if unset.
Success
{ "ok": true, "updated": true }
Errors

404 outside localhost or if DEV_VERIFY_SECRET is unset; 401 if the bearer does not match.

curl
curl -sS -X POST http://127.0.0.1:8080/api/dev/verify-email \
  -H "Authorization: Bearer $DEV_VERIFY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com"}'
JavaScript
await fetch("http://127.0.0.1:8080/api/dev/verify-email", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.DEV_VERIFY_SECRET}`,
  },
  body: JSON.stringify({ email: "dev@example.com" }),
});
Python
import os, requests
requests.post(
    "http://127.0.0.1:8080/api/dev/verify-email",
    json={"email": "dev@example.com"},
    headers={"Authorization": f"Bearer {os.environ['DEV_VERIFY_SECRET']}"},
    timeout=10,
)

Back to home