Skip to main content

Internal API Reference (v1)

Core REST endpoints exposed by InventoryAlert.Api. All routes are prefixed with /api/v1 unless otherwise noted. JWT Bearer Token required unless marked [Public].


Auth β€” /api/v1/auth​

Authentication via secure JWTs and httpOnly Refresh Token cookies.

MethodEndpointAuthDescription
POST/login[Public]Authenticate and receive AuthResponse + Refresh token via Cookie.
POST/register[Public]Create a new user account.
POST/refresh[Public]Exchange a valid httpOnly refresh token for a new access JWT.
POST/logoutJWTClears the refresh token cookie (server-side revocation hook reserved for future).

POST /login β€” Response​

{
"accessToken": "eyJhbGci...",
"expiresAt": "2026-04-14T07:29:43Z"
}

Access token TTL defaults to 60 minutes (config: Jwt:ExpiryMinutes). Refresh token TTL defaults to 7 days (config: Jwt:RefreshExpiryDays). Refresh token is delivered as an httpOnly cookie; Secure is enabled only on HTTPS, and SameSite is None for HTTPS/localhost (otherwise Lax).


Portfolio β€” /api/v1/portfolio​

Personal position management. All data scoped strictly to the authenticated user.

Positions are identified by (UserId, TickerSymbol) β€” no numeric position ID.

MethodEndpointDescription
GET/positionsList user's paged holdings and portfolio metrics.
GET/positions/{symbol}Detailed breakdown for a single ticker position.
GET/alertsPortfolio positions that breached defined thresholds.
POST/positionsOpen a new ownership position. Symbol must be in catalog first.
POST/bulkImport multiple positions at once.
POST/{symbol}/tradesRecord a trade (Buy/Sell/Dividend/Split) to adjust holdings.
DELETE/positions/{symbol}Remove position. Cascades user's Trades + WatchlistItem.

POST /positions β€” Request​

{
"tickerSymbol": "AAPL",
"quantity": 10,
"unitPrice": 172.50,
"tradedAt": "2024-11-01T14:30:00Z"
}

DELETE /positions/{symbol} β€” Cascade Scope​

ActionDeleted?
User's Trade ledger entries for this symbolβœ… Yes
User's WatchlistItem for this symbolβœ… Yes
StockListing (global catalog)❌ No
PriceHistory (global time-series)❌ No
StockMetric, EarningsSurprise, RecommendationTrend❌ No

POST /{symbol}/trades β€” Request​

Records an immutable Trade row for the authenticated user and returns the updated position snapshot.

{
"type": "Buy",
"quantity": 10,
"unitPrice": 172.5,
"notes": "Optional note (max 500 chars)"
}

Validation notes:

  • notes is optional and limited to 500 characters.
  • unitPrice must be >= 0 (dividend/split can be 0).
  • For Sell, the API rejects oversell attempts when netHoldings < quantity.

Stocks (Market Intelligence) β€” /api/v1/stocks​

Global market intelligence hub. Extensively cached locally to bypass strict external rate limits.

MethodEndpointDescription
GET/Browse full global StockListing catalog (paged, searchable).
GET/searchFuzzy symbol lookup by name, ISIN, or ticker (DB-first, Finnhub fallback).
GET/{symbol}/quoteReal-time price quote from cache (30s TTL) or Finnhub.
GET/{symbol}/profileCore company metadata (Logo, Industry, MarketCap, IPO date).
GET/{symbol}/financialsBasic financial metrics (P/E, P/B EPS, 52-week ranges, margins).
GET/{symbol}/earningsLast 4 quarters of reported Earnings Surprises (actual vs. estimate).
GET/{symbol}/recommendationAnalyst consensus recommendations trend over the last 3 months.
GET/{symbol}/insidersHistorical overview of the last 100 SEC-filed insider transactions.
GET/{symbol}/peersLists similar companies within the sector.
GET/{symbol}/newsLatest ticker-specific news from DynamoDB CompanyNews.
POST/sync[Admin] β€” Manually trigger global price sync job.

Symbol Discovery Strategy (DB-First)​


Market β€” /api/v1/market​

Exchange-level and calendar data.

MethodEndpointAuthDescription
GET/statusJWTCurrent open/closed status of major exchanges (US, LSE, HKEX).
GET/newsJWTGlobal financial news feed by category (market pulse).
GET/holidayJWTUpcoming and past market holidays by exchange.
GET/calendar/earningsJWTUpcoming earnings release calendar (1-month window).
GET/calendar/ipoJWTUpcoming and recent IPOs.

Watchlist β€” /api/v1/watchlist​

Watch-only tracking without requiring an open position.

MethodEndpointDescription
GET/List user's watchlist with live prices attached.
GET/{symbol}Single watchlist item with current metrics.
POST/{symbol}Add a ticker to the user's watchlist (DB-first symbol discovery).
DELETE/{symbol}Remove a ticker from watchlist.

Alert Rules β€” /api/v1/alertrules​

Configure dynamic evaluation triggers executed globally by background workers.

MethodEndpointDescription
GET/List all alert rules for the authenticated user.
POST/Create a new alert rule (symbol is auto-resolved if missing from catalog).
PUT/{id}Full replacement of an existing rule (id is a GUID).
PATCH/{id}/toggleEnable or disable without modifying rule parameters (id is a GUID).
DELETE/{id}Permanently remove an alert rule (id is a GUID).

Alert Conditions​

ConditionLogic
PriceAboveTrigger when current price > TargetValue
PriceBelowTrigger when current price < TargetValue
PriceTargetReachedTrigger when price hits TargetValue (Β±tolerance)
PercentDropFromCostTrigger when unrealized loss % > TargetValue (0.01–100)
LowHoldingsCountTrigger when user's share count < TargetValue (whole number)

Notifications β€” /api/v1/notifications​

In-app notification delivery. UI polls every 30 seconds.

MethodEndpointDescription
POST/test-signalr?message=...Dev helper: pushes a test notification via SignalR to the current user.
GET/Chronological notification feed. Pass ?onlyUnread=true for focused rendering.
GET/unread-countReturns int for navbar bell badge.
PATCH/{id}/readMark one notification as acknowledged (204 No Content).
PATCH/read-allBatch acknowledge all notifications.
DELETE/{id}Permanently dismiss a notification (204 No Content).

Events β€” /api/v1/events [Admin]​

Internal event publishing for SQS integration.

MethodEndpointDescription
POST/Publish an integration event to SQS. Returns 202 Accepted.
GET/typesList all supported event type strings.

Error Reference​

CodeHTTP StatusDescription
NotFound404Resource (symbol, rule, position) does not exist.
Conflict409Duplicate create or locked state (e.g., active alert blocks position delete).
BadRequest400Validation failed (FluentValidation).
Unauthorized401Missing or invalid JWT.
Forbidden403Authenticated but lacks ownership of resource.
UnprocessableEntity422Semantic error (e.g., negative quantity, bad ticker).
Internal500Unhandled server error.

Standard Error Body​

{
"errorCode": "BadRequest",
"userFriendlyMessage": "One or more validation errors occurred.",
"errors": {
"TickerSymbol": ["TickerSymbol must not be empty."],
"Quantity": ["Quantity must be greater than 0."]
}
}