Skip to main content

Admin API Reference

The Admin API provides CRUD operations for trigger rules, tenant overrides, event-source defaults, event schemas, and flows. It runs as a separate process (make run-api) from the Kafka consumer.

Common base URLs:

  • direct local binary / YAML config: http://localhost:8787
  • Docker host mapping in this repo: http://localhost:8090

The process port itself is controlled by api.port / API_PORT. In Docker, the process often listens on 8080 inside the container while the host calls 8090.


Endpoints

Health

MethodPathDescription
GET/healthHealth check

Response: 200 OK

{ "status": "ok" }

Triggers

MethodPathDescription
GET/api/v1/triggersList all rules
POST/api/v1/triggersCreate a new rule
GET/api/v1/triggers/{id}Get rule by ID
PUT/api/v1/triggers/{id}Update rule
DELETE/api/v1/triggers/{id}Delete rule
PUT/api/v1/triggers/{id}/enabledToggle enabled state

POST /api/v1/triggers

Request body:

{
"name": "New Rule",
"source": { "type": "kafka", "topic": "events-topic" },
"conditions": {
"event_type": "player_first_casino_state",
"expression": "payload.rounds_count > 10"
},
"output_topic": "triggers--my-output",
"output_mapping": {
"mode": "select",
"fields": [{ "source": "player_id", "target": "pid" }]
}
}
  • id is auto-generated (UUID) if not provided
  • output_mapping is optional
  • CEL expression is validated against the schema for the event_type if a schema exists

Response: 201 Created — Rule object with generated ID

Errors:

  • 400 — Invalid body, invalid CEL expression, or invalid output mapping
  • 409 — Rule with this ID already exists

PUT /api/v1/triggers/{id}

Request body: Full rule object (ID in path is used, body id is ignored)

Response: 200 OK — Updated rule

Errors:

  • 400 — Invalid body, invalid CEL expression, or invalid output mapping
  • 404 — Rule not found
  • 409 — Rule is managed by a flow (edit the flow instead)

DELETE /api/v1/triggers/{id}

Response: 204 No Content

Errors:

  • 404 — Rule not found
  • 409 — Rule is managed by a flow (delete the flow instead)

PUT /api/v1/triggers/{id}/enabled

Request body:

{ "enabled": false }

Response: 200 OK

{ "status": "ok" }

Errors: 404 — Rule not found


Tenant Overrides

MethodPathDescription
GET/api/v1/triggers/{id}/tenantsList tenant overrides
PUT/api/v1/triggers/{id}/tenants/{tenant_id}Upsert tenant override
POST/api/v1/triggers/{id}/tenants/{tenant_id}Upsert tenant override (alias)

PUT /api/v1/triggers/{id}/tenants/{tenant_id}

Request body:

{
"tenant_id": "2",
"enabled": true
}
  • tenant_id in body is ignored; path parameter is used
  • enabled is optional (omit to keep prior behavior for that tenant)

Response: 200 OK

{ "status": "ok" }

Errors: 404 — Rule not found


Event Sources (default output topic per Kafka source topic)

MethodPathDescription
GET/api/v1/event-sourcesList all event source defaults
GET/api/v1/event-sources/{source_topic}Get by source topic
PUT/api/v1/event-sources/{source_topic}Upsert default output topic

The engine uses default_output_topic when a rule's output_topic is empty. After changing data via the API, the consumer reloads caches on its periodic tick.

PUT /api/v1/event-sources/{source_topic}

Request body:

{
"default_output_topic": "triggers--loyalty-rank-up",
"description": "optional"
}
  • default_output_topic is required (non-empty after trim)

Response: 200 OK

Errors: 400 — Missing/empty default_output_topic


Event Schemas (CEL field definitions per event type)

MethodPathDescription
GET/api/v1/schemasList all schemas
GET/api/v1/schemas/{event_type}Get schema by event type
PUT/api/v1/schemas/{event_type}Upsert schema
DELETE/api/v1/schemas/{event_type}Delete schema

Schemas define the fields and types available in CEL expressions for a given event type.

PUT /api/v1/schemas/{event_type}

Request body:

{
"fields": [
{ "name": "player_id", "type": "int" },
{ "name": "rounds_count", "type": "int" },
{ "name": "is_active", "type": "bool" }
]
}

Supported field types: int, double, string, bool.

Response: 200 OK

Errors:

  • 400 — Empty fields array or invalid field type
  • 404 — Schema not found (for GET/DELETE)

CEL Expression Validation

MethodPathDescription
POST/api/v1/validate/expressionValidate a CEL expression

Request body:

{
"event_type": "player_first_casino_state",
"expression": "payload.rounds_count > 10"
}

Response: 200 OK

{ "valid": true, "errors": [] }

or

{ "valid": false, "errors": ["expression must return bool, got int"] }

Errors:

  • 400 — Missing event_type or unknown event type (no schema)

Flows (Studio graph model)

MethodPathDescription
GET/api/v1/flowsList all flows
POST/api/v1/flowsCreate a new flow
GET/api/v1/flows/{id}Get flow by ID
PUT/api/v1/flows/{id}Update flow
DELETE/api/v1/flows/{id}Delete flow and related flow-owned data
PUT/api/v1/flows/{id}/enabledToggle flow enabled state (propagates to compiled rules)
POST/api/v1/flows/{id}/validateValidate flow graph structure
POST/api/v1/flows/{id}/compileCompile flow into engine-executable rules

POST /api/v1/flows

Request body:

{
"name": "Bad Start Flow",
"source": {
"node_id": "src",
"event_type": "player_first_casino_state",
"source_topic": "player-first-casino-state.v1"
},
"conditions": [
{
"node_id": "c1",
"label": "High Loss",
"expression": "payload.dead_rounds_count > 50"
},
{ "node_id": "c2", "label": "Else", "is_else": true }
],
"outputs": [
{
"node_id": "o1",
"label": "Notify",
"output_topic": "triggers--bad-start-notify"
},
{
"node_id": "o2",
"label": "Log Only",
"output_topic": "triggers--bad-start-log"
}
],
"edges": [
{ "id": "e1", "from": "src", "to": "c1" },
{ "id": "e2", "from": "src", "to": "c2" },
{ "id": "e3", "from": "c1", "to": "o1" },
{ "id": "e4", "from": "c2", "to": "o2" }
]
}
  • id is auto-generated (UUID) if not provided
  • status defaults to "draft"

Response: 201 Created

POST /api/v1/flows/{id}/validate

Validates the flow graph structure without compiling.

Response: 200 OK

{ "valid": true, "errors": [] }

or

{
"valid": false,
"errors": [
"source: missing event_type",
"output \"o1\": missing output_topic"
]
}

POST /api/v1/flows/{id}/compile

Validates and compiles the flow into flat rules. Deletes any previously compiled rules, creates new ones, and updates the flow status to "active".

Response: 200 OK

{
"status": "compiled",
"rule_ids": ["flow_abc_c1_o1", "flow_abc_c2_o2"],
"rule_count": 2
}

Errors:

  • 400 — Validation failed (returns validation errors)
  • 404 — Flow not found

DELETE /api/v1/flows/{id}

Deletes the flow and all related compiled rules, versions, release attempts, and edit lock.

Response: 204 No Content


Error Format

{ "error": "human-readable message" }

Operational Notes

  • At-least-once: Kafka redelivery can cause duplicate outbound publishes; downstream consumers should be idempotent.
  • Skip when no topic: If neither rule output_topic nor a default for the source topic exists, the engine skips dispatch (warn log).
  • Flow-managed rules: Rules compiled from flows cannot be directly updated or deleted via the triggers API. Edit the flow and recompile instead.
  • Compilation is idempotent: Recompiling a flow produces the same deterministic rule IDs (flow_{flowID}_{condNodeID}_{outNodeID}).

Components catalog

POST /api/v1/components/register

Service-side sdk.RegisterOnBoot POSTs a ServiceManifest here on boot and every 5 minutes after. Payload cap is 1 MiB.

Responses:

  • 200 {"status":"ok"} — manifest accepted (new registration or heartbeat).
  • 400 — malformed manifest (validation, invalid action config_schema, etc.).
  • 409 — immutability violation: a component with the same (service, id, version) already exists with different content.
  • 413 — payload exceeds 1 MiB cap.

GET /api/v1/components

Returns every row of triggers_component_registry sorted by (service, component_id, version). Each row is a ComponentManifestRow wrapping a ComponentEntry plus first_registered_at, last_seen_at, and (when a previous version existed) a triggers-computed server_compatibility report.

GET /api/v1/components/:id/data/:source

Proxies a Studio form query through to the owning service's HTTP endpoint. The triggers server:

  1. Looks up the latest manifest row for component :id.
  2. Resolves data_sources[name=:source] on that entry.
  3. Joins the service's transports.http.endpoint with the data-source's path.
  4. Forwards only the query params listed in data_sources[*].params (anything else is dropped).
  5. Returns the upstream body plus the upstream Content-Type.

Upstream non-2xx yields 502 Bad Gateway. Unknown :id or :source also yields 502 with a descriptive error.