CognitiveX Docs

Reflexes

List, create, edit, fire, duplicate, confirm, inspect, and cost-account situation-triggered reflexes over the REST API.

A reflex is a stored situation-to-response trigger. You describe a situation (a scheduled time, or a cognition signal like a contradiction or a detected pattern) and the response to take when it fires (notify a channel, run an inference). iCog matches incoming situations against your reflexes and fires the ones that clear their threshold. This page is the contract for the reflex REST surface: request and response shapes, auth, and the meaningful failure modes. For the conceptual model, see Reflexes.

Endpoints

MethodPathPurpose
GET/api/reflexesList your reflexes (or reminders with kind=reminder).
GET/api/reflexes/{reflex_id}Fetch one reflex in the inspector row shape.
POST/api/reflexesCreate or update a reminder from a structured spec.
PUT/api/reflexes/{reflex_id}Full edit (situation, response, threshold, active state).
PATCH/api/reflexes/{reflex_id}Partial update (toggle active, set channel, rename).
DELETE/api/reflexes/{reflex_id}Soft-delete (pause and deregister).
POST/api/reflexes/{reflex_id}/fireDispatch the reflex now, independent of its trigger.
POST/api/reflexes/{reflex_id}/duplicateServer-side copy of an owned reflex.
POST/api/reflexes/confirmations/{intent_id}/decisionRecord a confirmation decision (fire / skip).
POST/api/reflexes/feedbackRecord post-fire feedback on an execution.
GET/api/reflexes/inspectorFilterable listing with inspector columns.
GET/api/reflexes/inspector/{reflex_id}/executionsExecution history for one reflex.
POST/api/reflexes/inspector/bulk-pausePause up to 100 reflexes.
POST/api/reflexes/inspector/bulk-resumeResume up to 100 reflexes.
POST/api/reflexes/inspector/bulk-deleteSoft-delete up to 100 reflexes.
GET/api/reflexes/{reflex_id}/costsPer-reflex daily cost series and 30-day projection.
GET/api/reflexes/aggregate-costTop reflexes by spend over a window.

Base URL and auth

All paths are under https://api.cognitivx.io. Every endpoint requires a key, passed as either header:

Authorization: Bearer YOUR_API_KEY

or

X-API-Key: YOUR_API_KEY

Create keys at developers.cognitivx.io/keys. If you operate inside an organization, add X-Org-ID: <org-id> to scope the call. Reflexes are owned by the calling user: reads and writes are scoped to your own rows, and a reflex you do not own returns 404 (never 403), so the surface never leaks the existence of another user's reflex ids. The single exception is GET /api/reflexes/{id}/costs, documented below.

Two parts of this surface sit behind server feature flags. The inspector routes return 404 unless COGIX_V12_REFLEX_INSPECTOR_ENABLED is on. The reminder convenience routes (GET /api/reflexes?kind=reminder, POST /api/reflexes, PATCH /api/reflexes/{id}) return 503 {"error": "feature_disabled"} unless COGIX_V12_REMINDERS_ENABLED is on. The core CRUD, fire, duplicate, and cost routes are always available.

There is no reflexes namespace in @cognitivx/sdk and no cogx CLI command for reflexes. This surface is REST-only today. The examples below use curl.

List reflexes

List your reflexes, newest first.

GET /api/reflexes

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
kindstringnoFilter by kind. Passing kind=reminder switches to the reminder response shape and is gated by COGIX_V12_REMINDERS_ENABLED.
statusstringnoupcoming | past | paused. Only applied in the reminder shape.
sortstringnonext_fire_asc (default). Reminder shape only.
limitintegernoPage size. Default 50.
offsetintegernoPage offset. Default 0.

Calls without kind return the legacy list shape and stay available regardless of feature flags.

curl https://api.cognitivx.io/api/reflexes \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "reflexes": [
    {
      "id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
      "situation_description": "user mentions a flight booking",
      "response_description": "remind them to check in 24h before departure",
      "match_threshold": 0.6,
      "durability_mode": "at_least_once",
      "is_active": true,
      "created_at": "2026-06-10T14:02:11.481222+00:00",
      "updated_at": "2026-06-10T14:02:11.481222+00:00",
      "calibration_event_count": 3
    }
  ]
}

When you pass kind=reminder (and the flag is on), each row is rendered with a computed next_fire_at in your timezone:

curl "https://api.cognitivx.io/api/reflexes?kind=reminder&status=upcoming" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "reminders": [
    {
      "id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
      "kind": "reminder",
      "description": "stand-up nudge",
      "cron": "30 9 * * 1-5",
      "one_shot": false,
      "timezone": "America/Toronto",
      "channel_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
      "is_active": true,
      "status": null,
      "next_fire_at": "2026-06-17T09:30:00-04:00",
      "last_fired_at": null,
      "created_at": "2026-06-10T14:02:11.481222+00:00",
      "updated_at": "2026-06-10T14:02:11.481222+00:00"
    }
  ],
  "count": 1
}

In the reminder shape, limit/offset paginate the SQL fetch before the upcoming 7-day window post-filter, so a page can come back shorter than limit even when more matching rows exist. last_fired_at is always null in this shape today.

Errors

StatusWhen
503kind=reminder requested while COGIX_V12_REMINDERS_ENABLED is off. Body {"error": "feature_disabled"}.
401Missing or invalid key.

Get a single reflex

Return one reflex in the inspector row shape (resolved trigger and action fields, plus a computed next_fire).

GET /api/reflexes/{reflex_id}

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
reflex_iduuid (path)yesThe reflex to fetch.
curl https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44 \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
  "description": "stand-up nudge",
  "kind": "reminder",
  "subtype": null,
  "trigger_type": "time_based",
  "action_type": "notify",
  "status": null,
  "is_active": true,
  "signal_source": null,
  "channel_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
  "last_fired_at": "2026-06-13T09:30:01.220000+00:00",
  "next_fire": "2026-06-17T09:30:00-04:00"
}

next_fire is computed only for time_based triggers that carry a cron; it is null otherwise. signal_source is the cognition signal name (for example contradiction, pattern, prediction) for event-based reflexes.

This route is declared last in the router so that the literal paths /aggregate-cost and /{id}/costs resolve first. A reflex id that collides with those words still routes correctly.

Errors

StatusWhen
404Reflex not found, or not owned by the caller.
401Missing or invalid key.

Create a reminder reflex

Create (or update) a reminder from a structured spec. This bypasses the conversational parser, so cron, one-shot, and timezone are taken verbatim.

POST /api/reflexes

Auth: Authorization: Bearer <key> or X-API-Key: <key>. Gated by COGIX_V12_REMINDERS_ENABLED.

FieldTypeRequiredDescription
descriptionstringyesWhat to remind. Min length 1.
cronstringyesA valid cron expression. Validated with the same library the scheduler uses.
one_shotbooleanyesFire once then complete, or repeat on the cron.
timezonestringyesIANA zone (for example America/Toronto).
kindstringnoMust be reminder (the only accepted value; default reminder).
channel_iduuid | nullnoNotification channel to fire to. Must belong to you. Omit/null to fall back to your default channel at fire time.
existing_reflex_iduuid | nullnoWhen set, updates that reflex instead of inserting a new row.
curl -X POST https://api.cognitivx.io/api/reflexes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "stand-up nudge",
    "cron": "30 9 * * 1-5",
    "one_shot": false,
    "timezone": "America/Toronto"
  }'
{
  "reflex_id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
  "kind": "reminder",
  "status": null,
  "is_active": true,
  "channel_id": null,
  "next_fire_at": "2026-06-17T09:30:00-04:00"
}

Errors

StatusWhen
400{"error": "invalid_cron", ...}, {"error": "invalid_timezone", ...}, or {"error": "channel_not_owned", ...}.
404existing_reflex_id does not exist or is not owned by the caller.
503COGIX_V12_REMINDERS_ENABLED off ({"error": "feature_disabled"}), or the reflex parser is not configured.

Edit a reflex

Full edit of a reflex's situation, response, threshold, and active state. Changing situation_description re-embeds the situation atomically inside the same transaction.

PUT /api/reflexes/{reflex_id}

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

FieldTypeRequiredDescription
situation_descriptionstring | nullnoNew situation text. Triggers an embedding recompute.
response_descriptionstring | nullnoNew response text.
match_thresholdnumber | nullno0.0–1.0. Raise it to make the reflex fire less eagerly.
is_activeboolean | nullnoPause (false) or resume (true).

Omitted fields are left untouched. Sending an all-empty body returns {"status": "noop"}.

curl -X PUT https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"match_threshold": 0.75}'
{ "status": "ok" }

Errors

StatusWhen
404Reflex not found, or not owned by the caller.
422match_threshold outside 0.0–1.0.

Patch a reflex

Minimal partial update for the common toggles: active state, target channel, and rename. Other shape edits go through PUT above or the parser flow.

PATCH /api/reflexes/{reflex_id}

Auth: Authorization: Bearer <key> or X-API-Key: <key>. Gated by COGIX_V12_REMINDERS_ENABLED.

FieldTypeRequiredDescription
is_activeboolean | nullnoToggle active state. Reconciles the scheduler.
channel_iduuid | nullnoSend null to clear the channel (fall back to default); omit to leave unchanged.
descriptionstring | nullnoRename. Sets both situation and response text.

channel_id distinguishes omitted from null. Omitting the key leaves the column untouched; sending "channel_id": null clears it.

curl -X PATCH https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'
{
  "reflex_id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
  "kind": "reminder",
  "status": null,
  "is_active": false,
  "channel_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
  "next_fire_at": "2026-06-17T09:30:00-04:00"
}

An empty body returns {"status": "noop", "reflex_id": "..."}.

Errors

StatusWhen
400{"error": "channel_not_owned", ...} when channel_id is set to a channel you do not own.
404Reflex not found, or not owned by the caller.
503COGIX_V12_REMINDERS_ENABLED is off ({"error": "feature_disabled"}).

Delete (pause) a reflex

Soft-delete. The reflex is flipped to is_active = false and deregistered from the scheduler. The row is not hard-deleted, so its history and costs stay queryable.

DELETE /api/reflexes/{reflex_id}

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

curl -X DELETE https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44 \
  -H "Authorization: Bearer YOUR_API_KEY"
{ "status": "ok" }

Errors

StatusWhen
404Reflex not found, or not owned by the caller.

Fire a reflex now

Manually dispatch a reflex immediately, independent of its trigger. Thin wrapper over the executor.

POST /api/reflexes/{reflex_id}/fire

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

curl -X POST https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44/fire \
  -H "Authorization: Bearer YOUR_API_KEY"
{ "fired": true }

fired is false when the executor is unavailable in the running environment or an engine flag gates the dispatch; the call still returns 200.

Errors

StatusWhen
404Reflex not found, or not owned by the caller.

Duplicate a reflex

Server-side copy of an owned reflex. The copy keeps the match strategy, durability mode, and the full structured intent (trigger config, cron, action), starts is_active = true with a fresh id and timestamps, and resets its calibration counters.

POST /api/reflexes/{reflex_id}/duplicate

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

curl -X POST https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44/duplicate \
  -H "Authorization: Bearer YOUR_API_KEY"

The response is the new reflex in the same inspector row shape returned by GET /api/reflexes/{id}:

{
  "id": "c41e77b0-9d6b-4f3a-8a21-0e9f3b7c6512",
  "description": "stand-up nudge",
  "kind": "reminder",
  "subtype": null,
  "trigger_type": "time_based",
  "action_type": "notify",
  "status": null,
  "is_active": true,
  "signal_source": null,
  "channel_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
  "last_fired_at": null,
  "next_fire": "2026-06-17T09:30:00-04:00"
}

Errors

StatusWhen
404Source reflex not found, or not owned by the caller.

Confirmation decisions (fire / skip)

When a reflex surfaces a confirmation (a "should I do this?" prompt), record the decision here. fire dispatches it; skip_once declines this instance; skip_always declines and raises the reflex's match_threshold by 0.05 (capped at 0.95) so it fires less eagerly next time.

POST /api/reflexes/confirmations/{intent_id}/decision

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

FieldTypeRequiredDescription
intent_iduuid (path)yesThe execution/intent id the confirmation referenced.
decisionstringyesfire | skip_once | skip_always.
curl -X POST https://api.cognitivx.io/api/reflexes/confirmations/9c7e1f02-3a4b-4c5d-8e6f-7a8b9c0d1e2f/decision \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"decision": "skip_always"}'
{
  "status": "ok",
  "decision": "skip_always",
  "threshold_bumped": true,
  "fired": false
}

threshold_bumped is true only for skip_always. fired is true only when decision is fire and the executor actually dispatched.

Errors

StatusWhen
404No matching intent/execution.

Feedback

Record post-fire feedback on an execution. The signals are thumbs_up / thumbs_down / retracted. This endpoint exists for surfaces that confirm a reflex after it has fired (inbox, push, inspector).

POST /api/reflexes/feedback

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

FieldTypeRequiredDescription
execution_iduuidyesThe execution being rated. Must be yours.
user_labelstringyesthumbs_up | thumbs_down | retracted.
surfacestringnotelegram_inline | inbox | web_push | inspector | tier4_decision. Default inspector.
notestring | nullnoFree-text note.
curl -X POST https://api.cognitivx.io/api/reflexes/feedback \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "execution_id": "7a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
    "user_label": "thumbs_up",
    "surface": "inbox"
  }'
{ "status": "ok" }

Errors

StatusWhen
404Execution not found, or not owned by the caller.

The inspector

The inspector is a read/manage surface over your reflexes: filterable listing, per-reflex execution history, and bulk pause/resume/delete. Every inspector route is gated by COGIX_V12_REFLEX_INSPECTOR_ENABLED and returns 404 when the flag is off (the surface stays invisible rather than returning 403).

List inspector rows

GET /api/reflexes/inspector

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
kindstringnoFilter by kind.
trigger_typestringnoFor example time_based, event_based.
action_typestringnoFor example notify, llm_inference.
statusstringnoDirect status column match.
signal_sourcestringnoCognition signal name (for example contradiction, pattern).
qstringnoCase-insensitive substring match on the reflex description.

Rows are ordered by last fire time (most recent first), falling back to creation time, and capped at 200.

curl "https://api.cognitivx.io/api/reflexes/inspector?action_type=notify" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "reflexes": [
    {
      "id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
      "description": "stand-up nudge",
      "kind": "reminder",
      "subtype": null,
      "trigger_type": "time_based",
      "action_type": "notify",
      "status": null,
      "is_active": true,
      "signal_source": null,
      "channel_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
      "last_fired_at": "2026-06-13T09:30:01.220000+00:00",
      "next_fire": "2026-06-17T09:30:00-04:00"
    }
  ]
}

status is a direct column match. The status column only ever holds null or fired_complete, so filtering status=paused matches zero rows. Pause is represented as is_active = false, not as a status value.

Execution history

Chronological execution history for one reflex, newest first.

GET /api/reflexes/inspector/{reflex_id}/executions

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
reflex_iduuid (path)yesThe reflex whose executions you want.
limitintegerno1–500. Default 100.
curl "https://api.cognitivx.io/api/reflexes/inspector/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44/executions?limit=2" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "executions": [
    {
      "id": "e1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8",
      "fired_at": "2026-06-13T09:30:01.220000+00:00",
      "trigger_signal": "scheduler",
      "outcome": "delivered",
      "composite_score": 0.82,
      "error": null
    },
    {
      "id": "f2b3c4d5-e6f7-4081-9203-b4c5d6e7f8a9",
      "fired_at": "2026-06-12T09:30:00.901000+00:00",
      "trigger_signal": "scheduler",
      "outcome": "delivered",
      "composite_score": 0.80,
      "error": null
    }
  ]
}

The response renames the underlying DB columns: trigger_source becomes trigger_signal, and error_detail becomes error.

Errors

StatusWhen
404Reflex not found, not owned, or the inspector flag is off.

Bulk pause, resume, delete

Three sibling endpoints, all taking the same body and all owner-scoped. Foreign reflex ids are silently dropped from the count rather than rejected.

POST /api/reflexes/inspector/bulk-pause — sets is_active = false on active rows. Already-inactive rows are skipped, so a second call returns paused_count: 0.

POST /api/reflexes/inspector/bulk-resume — sets is_active = true on inactive rows. Returns resumed_count.

POST /api/reflexes/inspector/bulk-delete — soft-delete (is_active = false), with no active-state guard, so it counts a row whether it was active or already paused. Returns deleted_count.

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

FieldTypeRequiredDescription
reflex_idsuuid[]yes1–100 reflex ids.
curl -X POST https://api.cognitivx.io/api/reflexes/inspector/bulk-pause \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reflex_ids": ["8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44"]}'
{ "paused_count": 1 }

bulk-resume returns {"resumed_count": N} and bulk-delete returns {"deleted_count": N}.

Errors

StatusWhen
404Inspector flag is off.
422reflex_ids empty or longer than 100.

Costs

Reflexes that run inference accrue cost. Two read endpoints expose it. Both are owner-scoped and always available (no feature flag).

Per-reflex cost over time

One row per calendar day across the window, with zero-cost days filled in so a chart has no gaps. projection_30d linearly extrapolates from the last 7 days of actual spend.

GET /api/reflexes/{reflex_id}/costs

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
reflex_iduuid (path)yesThe reflex to report on.
daysintegernoWindow length, 1–365. Default 30.
curl "https://api.cognitivx.io/api/reflexes/8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44/costs?days=7" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "reflex_id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
  "window_days": 7,
  "total_cost_usd": 0.0143,
  "daily": [
    { "day": "2026-06-10", "cost_usd": 0.0, "call_count": 0 },
    { "day": "2026-06-11", "cost_usd": 0.0041, "call_count": 2 },
    { "day": "2026-06-12", "cost_usd": 0.0051, "call_count": 2 },
    { "day": "2026-06-13", "cost_usd": 0.0051, "call_count": 2 },
    { "day": "2026-06-14", "cost_usd": 0.0, "call_count": 0 },
    { "day": "2026-06-15", "cost_usd": 0.0, "call_count": 0 },
    { "day": "2026-06-16", "cost_usd": 0.0, "call_count": 0 }
  ],
  "projection_30d": 0.0613
}

Errors

StatusWhen
400days outside 1–365.
403Reflex exists but is owned by another user.
404Reflex not found.

This is the one reflex route that distinguishes 403 (exists, not yours) from 404 (does not exist). The list, fire, inspector, and other routes return 404 for both cases.

Aggregate cost across reflexes

Top reflexes by spend over the window, descending, capped at 50. Only reflexes with non-zero spend are returned.

GET /api/reflexes/aggregate-cost

Auth: Authorization: Bearer <key> or X-API-Key: <key>.

ParamTypeRequiredDescription
daysintegernoWindow length, 1–365. Default 30.
curl "https://api.cognitivx.io/api/reflexes/aggregate-cost?days=30" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "window_days": 30,
  "total_cost_usd": 0.2417,
  "rows": [
    {
      "reflex_id": "8f2c1a9e-6b34-4d2a-9f10-2c7b5e0a1d44",
      "description": "summarize new docs each morning",
      "action_type": "llm_inference",
      "total_cost_usd": 0.2104,
      "call_count": 31,
      "last_fired_at": "2026-06-16T13:00:00.500000+00:00"
    },
    {
      "reflex_id": "c41e77b0-9d6b-4f3a-8a21-0e9f3b7c6512",
      "description": "stand-up nudge",
      "action_type": "notify",
      "total_cost_usd": 0.0313,
      "call_count": 22,
      "last_fired_at": "2026-06-13T09:30:01.220000+00:00"
    }
  ]
}

Errors

StatusWhen
400days outside 1–365.

Common error shapes

StatusMeaning
400Invalid input. Validation failures from the create/patch routes carry a {"error": "<code>", ...} body (for example invalid_cron, invalid_timezone, channel_not_owned).
401Missing or invalid API key.
403Used only by GET /api/reflexes/{id}/costs when the reflex exists but belongs to another user.
404Reflex (or intent/execution) not found or not owned. Also returned by every inspector route when the inspector flag is off.
422FastAPI request validation error (out-of-range match_threshold, malformed uuid, oversized reflex_ids).
503A feature flag is off ({"error": "feature_disabled"}) or a required engine component (parser) is not configured.

See also