Skip to main content

Signal Ingest

Submit trading signals to UTM for execution.

POST /api/signals/ingest

Authentication

Requires an API key with signals:write scope in the X-API-Key header.

curl -X POST https://api.example.com/api/signals/ingest \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"symbol": "AAPL", "action": "openLong", ...}'

Request Body

Required Fields

FieldTypeDescription
symbolstringTrading symbol (1-20 chars, auto-uppercased)
actionenumSignal action: openLong, closeLong, openShort, closeShort
accountIdUUIDTarget trading account ID
quantitynumberOrder quantity (must be positive)
processAtstring"now" for immediate, or ISO 8601 datetime for scheduled

Optional Fields

FieldTypeDefaultDescription
strategyIdUUID-Associated strategy ID
quantityTypeenum"fixed"fixed, percentEquity, dollarAmount
orderTypeenum"market"market, limit, stop, stop_limit
limitPricenumber-Required for limit and stop_limit orders
stopPricenumber-Required for stop and stop_limit orders
timeInForceenum"day"day, gtc, opg, cls, ioc, fok, gtd
extendedHoursbooleanfalseAllow pre/post-market trading
timezonestring-IANA timezone (required when processAt is datetime)
expiresAtISO datetime-Signal expiration (requires timezone)
sourceIdstring-Idempotency key (max 255 chars, scoped per account)
replacebooleanfalseReplace existing signal with same sourceId
metadataobject-Custom key-value pairs
exitRuleobject-Exit rule configuration

Exit Rules

Automatically create an exit signal when the entry order fills.

Market on Close (MOC)

Place an exit order that executes at market close.

Immediate MOC - Order placed immediately, executes at close:

{
"exitRule": {
"trigger": "market_close",
"immediate": true
}
}

Scheduled MOC - Order placed N minutes before close:

{
"exitRule": {
"trigger": "market_close",
"minutesBefore": 5
}
}
FieldTypeDefaultDescription
triggerenum-market_close or time
immediatebooleanfalsePlace MOC order immediately on fill
minutesBeforenumber0Minutes before close (0-30, ignored if immediate: true)
timeInForceenum"cls"day or cls

Time-Based Exit

Exit after a duration or at a specific time.

Duration after fill:

{
"exitRule": {
"trigger": "time",
"durationMinutes": 60
}
}

Specific time:

{
"exitRule": {
"trigger": "time",
"exitTime": "15:30"
}
}
FieldTypeDescription
triggerenumMust be time
durationMinutesnumberMinutes after fill to exit
exitTimestringSpecific time in HH:MM format
note

For time trigger, provide either durationMinutes or exitTime, not both.

Examples

Minimal Market Order

{
"symbol": "AAPL",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 100,
"processAt": "now"
}

Limit Order

{
"symbol": "TSLA",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 50,
"orderType": "limit",
"limitPrice": 250.0,
"processAt": "now"
}

Scheduled Signal with Immediate MOC Exit

{
"symbol": "MSFT",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"strategyId": "550e8400-e29b-41d4-a716-446655440001",
"quantity": 75,
"processAt": "2026-02-07T09:25:00",
"timezone": "America/New_York",
"sourceId": "my-strategy-signal-001",
"exitRule": {
"trigger": "market_close",
"immediate": true
}
}

Stop-Limit Order with Replace

{
"symbol": "GOOGL",
"action": "openShort",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 25,
"orderType": "stop_limit",
"stopPrice": 145.0,
"limitPrice": 144.0,
"processAt": "now",
"sourceId": "my-ref-456",
"replace": true
}

Dollar Amount Position Sizing

{
"symbol": "SPY",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 5000,
"quantityType": "dollarAmount",
"processAt": "now"
}

Response

Success - Immediate (201)

{
"message": "Signal processed successfully",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "executed",
"orderId": "550e8400-e29b-41d4-a716-446655440098"
}
}

Success - Scheduled (202)

{
"message": "Signal scheduled for processing",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "scheduled",
"scheduledAt": "2026-02-07T14:25:00.000Z"
}
}

Validation Error (400)

{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{
"code": "custom",
"message": "limitPrice is required for limit orders",
"path": ["limitPrice"]
}
]
}

Duplicate Signal (400)

{
"error": "Signal rejected",
"message": "Signal with sourceId 'my-ref' already exists with status 'PENDING'. Use replace=true to supersede.",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "pending"
},
"code": "SIGNAL_REJECTED"
}

Response Codes

CodeDescription
201Signal executed immediately
202Signal scheduled for future processing
400Validation error or signal rejected
401Missing or invalid API key
404Account or strategy not found
503Service disabled

Idempotency

Use sourceId to prevent duplicate signals:

  • If a signal with the same sourceId exists (non-rejected), the existing signal is returned
  • With replace: true, the existing signal is cancelled and a new one created
  • Same sourceId can exist on different accounts (scoped per account)
  • Only rejected signals allow retry with the same sourceId