Create Signal
Submit trading signals to UTM for execution, scheduling, or manual review.
POST /api/v1/signals
Authentication
Supports both authentication methods:
- JWT Bearer Token: For web UI and session-based access
- API Key: For webhooks and automation (requires
signals:writescope)
# Using JWT
curl -X POST https://api.universaltrademanager.com/api/v1/signals \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"symbol": "AAPL", "action": "openLong", "executeMode": "pending", ...}'
# Using API Key
curl -X POST https://api.universaltrademanager.com/api/v1/signals \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"symbol": "AAPL", "action": "openLong", "executeMode": "immediate", ...}'
Execute Mode
The executeMode field determines how the signal is processed:
| Mode | Description |
|---|---|
immediate | Process signal and place order immediately |
scheduled | Schedule for processing at scheduledProcessAt |
pending | Create in pending status for manual review/approval |
Request Body
Required Fields
| Field | Type | Description |
|---|---|---|
symbol | string | Trading symbol (1-20 chars, auto-uppercased) |
action | enum | Signal action: openLong, closeLong, openShort, closeShort |
accountId | UUID | Target trading account ID |
quantity | number | Order quantity (must be positive) |
executeMode | enum | How to process: immediate, scheduled, pending |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
scheduledProcessAt | ISO datetime | - | When to process (required for scheduled mode) |
timezone | string | - | IANA timezone (required when scheduledProcessAt is set) |
strategyId | UUID | - | Associated strategy ID |
quantityType | enum | "fixed" | fixed, percentEquity, dollarAmount |
orderType | enum | "market" | market, limit, stop, stopLimit |
limitPrice | number | - | Required for limit and stopLimit orders |
stopPrice | number | - | Required for stop and stopLimit orders |
timeInForce | enum | "day" | day, gtc, opg, cls, ioc, fok |
extendedHours | boolean | false | Allow pre/post-market trading |
validUntil | ISO datetime | - | Signal expiration (requires timezone) |
sourceId | string | - | Idempotency key (max 255 chars, scoped per account) |
replaceExisting | boolean | false | Replace existing signal with same sourceId |
metadata | object | - | Custom key-value pairs |
Timed Exit Fields
Automatically create an exit order after entry fills:
| Field | Type | Description |
|---|---|---|
exitTriggerType | enum | Exit trigger: timed |
exitTriggerValue | integer | Minutes after entry to exit |
exitOrderType | enum | Exit order type: market, limit |
exitLimitPrice | number | Limit price for exit (if limit order) |
Examples
Immediate Market Order
Execute a market order immediately:
{
"symbol": "AAPL",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 100,
"executeMode": "immediate"
}
Immediate with Timed Exit
Open position and close after 30 minutes:
{
"symbol": "AAPL",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "timed",
"exitTriggerValue": 30,
"exitOrderType": "market"
}
Scheduled Signal
Schedule signal for processing at a specific time:
{
"symbol": "MSFT",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 75,
"executeMode": "scheduled",
"scheduledProcessAt": "2026-02-07T09:25:00",
"timezone": "America/New_York",
"sourceId": "my-strategy-signal-001"
}
Pending Signal for Review
Create signal for manual review before execution:
{
"symbol": "TSLA",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 50,
"executeMode": "pending",
"orderType": "limit",
"limitPrice": 250.0
}
Limit Order with Replace
Replace existing signal with same sourceId:
{
"symbol": "GOOGL",
"action": "openShort",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 25,
"executeMode": "immediate",
"orderType": "stopLimit",
"stopPrice": 145.0,
"limitPrice": 144.0,
"sourceId": "my-ref-456",
"replaceExisting": true
}
Dollar Amount Position Sizing
{
"symbol": "SPY",
"action": "openLong",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 5000,
"quantityType": "dollarAmount",
"executeMode": "immediate"
}
Response
Success - Immediate Execution (201)
{
"success": true,
"message": "Signal processed successfully",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "executed",
"orderId": "550e8400-e29b-41d4-a716-446655440098",
"executeMode": "immediate"
}
}
Success - Scheduled (202)
{
"success": true,
"message": "Signal scheduled for processing",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "scheduled",
"scheduledAt": "2026-02-07T14:25:00.000Z",
"executeMode": "scheduled"
}
}
Success - Pending (201)
{
"success": true,
"message": "Signal created",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "pending",
"executeMode": "pending"
}
}
Validation Error (400)
{
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{
"code": "custom",
"message": "scheduledProcessAt is required for scheduled execute mode",
"path": ["scheduledProcessAt"]
}
]
}
Duplicate Signal (200)
When sourceId matches an existing non-rejected signal:
{
"success": true,
"message": "Signal with sourceId 'my-ref' already exists with status 'pending'. Use replaceExisting=true to supersede.",
"signal": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"status": "pending",
"executeMode": "pending"
}
}
Response Codes
| Code | Description |
|---|---|
200 | Existing signal returned (idempotent) |
201 | Signal created/executed |
202 | Signal scheduled for future processing |
400 | Validation error or signal rejected |
401 | Missing or invalid authentication |
404 | Account or strategy not found |
503 | Service disabled |
Idempotency
Use sourceId to prevent duplicate signals:
- If a signal with the same
sourceIdexists (non-rejected), the existing signal is returned - With
replaceExisting: true, the existing signal is cancelled and a new one created - Same
sourceIdcan exist on different accounts (scoped per account) - Only
rejectedsignals allow retry with the samesourceId
Processing Pending Signals
Signals created with executeMode: "pending" can be processed manually:
POST /api/v1/signals/:id/process
This endpoint requires JWT authentication and converts the pending signal into an order.