Decisions as a Service
Sign Up Now →Sign Up Now

HTTP API

The decision service puts the embedded engine behind REST, Server-Sent Events and WebSocket, so services in any language can use it. This page lists every endpoint, its JSON and its current limits.

Runtime 0.3.1Service version 1.1.0

About the example

The examples on this page use the built-in data-protection pack so you can run everything without writing a pack first. The same calls work for any decision pack: eligibility, limits, routing and so on. See Core concepts.

Running the service#

HowEngineDefault address
helixor-pack serve --pack … --license …Your compiled pack127.0.0.1:18734
python -m helixor_runtime.server [--pack … --license …]Your compiled pack, or the built-in example pack without --pack127.0.0.1:18734
start_server(engine) from PythonThe engine you pass; the built-in example pack if none127.0.0.1, random free port

The service evaluates in-process: no model calls and no outbound network traffic. Base URL in the examples: http://127.0.0.1:18734. See Services and Serve decisions to other languages.

Responses contain the raw matched values

Every evaluation response includes triggers[].matched_items: the exact values that matched. With the example pack, those are the unredacted SSNs, card numbers and addresses that were found. Treat responses as sensitive. Don't log response bodies, and strip matched_items before passing a result on to anything else.

Authentication and origins#

  • Bearer token. When the service has a token (--token, token= or HELIXOR_SERVICE_TOKEN), every request except GET /v1/health and CORS preflight must send Authorization: Bearer <token>. Otherwise HTTP answers 401 with {"detail": "Missing or invalid bearer token"} and a WWW-Authenticate: Bearer header, and a WebSocket is closed with code 1008. Without a token the service has no authentication.
  • Binding. The default host is 127.0.0.1. Starting on any non-loopback address without a token fails with ServerConfigurationError.
  • CORS. Off by default. HELIXOR_CORS_ORIGINS takes a comma-separated list of browser origins; credentials are allowed only for those. An origin containing * is rejected at startup.
  • One engine. A service that serves a compiled pack answers 400 to a request that sets license_key, so a caller cannot swap in another engine.

Changed in 0.3.0

WebSocket streams stay blocked. The connection remembers a block: every later stream_chunk is refused with an error message whose code is STREAM_BLOCKED, and nothing more is released until you send stream_start. A block found at stream_flush counts too. In 0.2.1 the next stream_chunk started a new session and the text after the fatal value was streamed back. See WS /v1/ws/decision.

Errors#

StatusWhenBody
400Unknown domain in POST /v1/decision, or license_key sent to a service that serves a compiled pack.{"detail": "Unknown decision function: …"} or {"detail": "This service executes sealed pack '…' under license '…'; per-request license_key is not accepted."}
401The service has a token and the request did not send it.{"detail": "Missing or invalid bearer token"}
403license_data fails verification, or does not allow the pack or solver.{"detail": "Invalid license: …"} or an entitlement message
422The request body fails validation, for example a missing text.Standard validation error list under detail.
500An unhandled error, for example an invalid license_key sent to a service running the built-in pack.Plain text.

See Errors. A client must treat any non-200 response, timeout or connection failure as "do not proceed".

GET /v1/health#

Liveness and metadata. It needs no token. pack_id and engine_edition (the license tier) describe the engine actually being served, so a readiness check can compare them with what you deployed. regulatory_rules lists the rules the served engine evaluates: the built-in checks, plus a compiled pack's own rules with "source": "pack". supported_regulations is derived from it. The response from the built-in pack on runtime 0.3.1:

{
  "status": "healthy",
  "service": "helixor-decision-demos",
  "version": "1.1.0",
  "engine_edition": "COMMUNITY",
  "pack_id": "compliance.regulatory_pii_guard.v1",
  "supported_protocols": ["REST", "SSE", "WebSocket"],
  "supported_regulations": ["CCPA", "FCRA", "GDPR", "GLBA", "HIPAA", "PCI-DSS", "TCPA"],
  "regulatory_rules": [
    {"rule_id": "RULE-GLBA-SSN-BLOCK", "regulations": ["FCRA", "GLBA"], "source": "builtin"},
    {"rule_id": "RULE-PCI-DSS-PAN-BLOCK", "regulations": ["PCI-DSS"], "source": "builtin"},
    {"rule_id": "RULE-HIPAA-PHI-BLOCK", "regulations": ["HIPAA"], "source": "builtin"},
    {"rule_id": "RULE-GDPR-EMAIL-REDACT", "regulations": ["CCPA", "GDPR"], "source": "builtin"},
    {"rule_id": "RULE-TCPA-PHONE-REDACT", "regulations": ["CCPA", "TCPA"], "source": "builtin"},
    {"rule_id": "RULE-GDPR-IP-REDACT", "regulations": ["GDPR"], "source": "builtin"}
  ],
  "decision_functions": ["evaluate", "solve_constraints", "vrp"]
}

Changed in 0.3.1

regulatory_rules is new, and supported_regulations now comes from the rules the engine evaluates instead of a fixed list.

POST /v1/evaluate#

Evaluates one payload and returns the full result.

FieldTypeRequiredMeaning
textstringyesThe payload.
license_keystringnoA Helixor key string, not a .hxlic file. On a service running the built-in pack, a new built-in engine is created for this request with that key. A service running a compiled pack rejects it with 400. Most deployments omit it.
POST /v1/evaluate HTTP/1.1
Content-Type: application/json

{"text": "Employee note: SSN is 123-45-6789, email jane@example.com."}
{
  "pack_id": "compliance.regulatory_pii_guard.v1",
  "action": "block_glba_ssn_leakage",
  "invariants_passed": false,
  "reason": "Statutory violation: Social Security Number (GLBA / FCRA) detected",
  "triggers": [
    {
      "rule_id": "RULE-GLBA-SSN-BLOCK",
      "law": "Gramm-Leach-Bliley Act & FCRA",
      "severity": "FATAL",
      "matched_items": ["123-45-6789"]
    },
    {
      "rule_id": "RULE-GDPR-EMAIL-REDACT",
      "law": "GDPR Article 6 & CCPA",
      "severity": "WARNING",
      "matched_items": ["jane@example.com"]
    }
  ],
  "remedy": {
    "clean_text": "Employee note: SSN is [REDACTED_SSN], email [REDACTED_EMAIL].",
    "redactions_count": 2,
    "redacted_categories": ["SSN (GLBA/FCRA)", "Email (GDPR/CCPA)"]
  },
  "latency_us": 70.12,
  "tokens_spent": 0,
  "egress_bytes": 0,
  "receipt_hash": "hx_proof_5b46797af4f9af1e982eabe8",
  "license_status": "COMMUNITY_PERPETUAL",
  "active_key_id": "hx_lic_community_developer_ep1_0",
  "active_epoch": 1,
  "edition": "COMMUNITY",
  "upgrade_notice": "Free Community Edition: …"
}

The first evaluation after start-up is slower than later ones; the latency above is from a first call. The fields are described under EvaluationResult in the Python reference.

curl -s http://127.0.0.1:18734/v1/evaluate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Card 4111-1111-1111-1111"}' | jq '{action, clean: .remedy.clean_text}'

POST /v1/evaluate/stream#

Returns text/event-stream. You send the whole text; the server cuts it into chunk_size-character pieces and pushes them through a streaming session. That simulates a token stream, which makes the endpoint useful for testing streaming behavior. It cannot filter a stream your application is receiving; use the WebSocket for that.

FieldTypeDefaultMeaning
textstringrequiredThe complete text.
chunk_sizeint4Characters per simulated fragment (minimum 1).
lookahead_charsint28Held-back window (minimum 16).
block_on_fatalbooltrueStop the stream on a fatal match.
license_keystringnoneAs for /v1/evaluate.

Each event is one line, data: <StreamChunkResult JSON>, followed by a blank line. After a "blocked": true event the stream ends.

data: {"chunk_index": 1, "text": "Call ", "is_final": false, "redacted": false, "blocked": false, "action": "stream_token", "reason": null, "triggers": []}

data: {"chunk_index": 2, "text": "[REDACTED_PHONE] today, ask for Jane.", "is_final": true, "redacted": true, "blocked": false, "action": "stream_token", "reason": null, "triggers": []}

That is the real output for {"text": "Call (415) 555-0100 today, ask for Jane.", "chunk_size": 6}. Chunk boundaries fall at delimiters the lookahead window allows, never inside a value or a redaction token. redacted is true on exactly the chunks whose text was changed. Concatenate chunk text before you display or store it; the result equals the /v1/evaluate clean_text.

POST /v1/decision#

A single dispatcher for decision functions, with optional license checks on each request.

FieldTypeDefaultMeaning
domainstringevaluateWhich function to run. evaluate (aliases pii, safety) is documented here.
parametersobject{}For evaluate: {"text": "…"}.
license_keystringnoneAs for /v1/evaluate.
license_dataobject or stringnoneYour .hxlic JSON. When given, it is verified, and the request is refused with 403 unless the license allows the served pack. Sending it sends your content key; only do so on a loopback or private link.
{"domain": "evaluate", "parameters": {"text": "Contact jane@example.com"}}
{
  "function": "evaluate",
  "status": "success",
  "latency_us": 31.75,
  "result": { "action": "redact_and_permit_contact_pii", "remedy": {"clean_text": "Contact [REDACTED_EMAIL]", "…": "…"}, "…": "…" }
}

result is the same object /v1/evaluate returns. The other domain values (vrp/fleet, bin_packing/packing, solve_constraints/solver) call engines that are out of scope for this edition of the reference. Unknown values return 400.

WS /v1/ws/decision#

A persistent connection for many evaluations or for live stream filtering. Send and receive one JSON object per message. The connection uses the service's engine (your pack under serve); license_key is not accepted here. When the service has a token, send it in the Authorization header of the upgrade request.

You sendYou receive
{"type": "ping"}{"type": "pong", "time": <unix seconds>}
{"type": "evaluate", "text": "…"}{"type": "verdict", "action", "invariants_passed", "clean_text", "latency_us", "egress_bytes", "receipt_hash"}. No triggers, so no raw values.
{"type": "stream_start", "lookahead_chars": 28, "block_on_fatal": true}{"type": "stream_started", "lookahead_chars": 28}. Starts a new streaming session for this connection.
{"type": "stream_chunk", "token": "…"}Zero or more {"type": "stream_token", "token", "blocked", "action", "reason"}. After "blocked": true the session ends, and every later chunk gets {"type": "error", "code": "STREAM_BLOCKED", "message", "action", "reason"} until the next stream_start. A chunk with no session and no block starts one with default settings.
{"type": "stream_flush"}The remaining stream_token messages, then {"type": "stream_completed"}. A flush after a block releases nothing and does not end the block; only stream_start does.
Any other type{"type": "error", "message": "Unknown message type: '…'"}

A message without type is treated as evaluate. Messages are handled in order on each connection. A {"type": "solve"} message also exists; it is out of scope for this edition. If a handler raises, the server sends {"type": "error"} and closes the connection.

→ {"type": "stream_start"}
← {"type": "stream_started", "lookahead_chars": 28}
→ {"type": "stream_chunk", "token": "Reach me at jane@example.com or on the phone any time this week please."}
← {"type": "stream_token", "token": "Reach me at [REDACTED_EMAIL] or on the ", "blocked": false, "action": "stream_token", "reason": null}
→ {"type": "stream_flush"}
← {"type": "stream_token", "token": "phone any time this week please.", "blocked": false, "action": "stream_token", "reason": null}
← {"type": "stream_completed"}

The same connection after a block:

→ {"type": "stream_start"}
← {"type": "stream_started", "lookahead_chars": 28}
→ {"type": "stream_chunk", "token": "SSN 123-45-6789 and more text to pass the window"}
← {"type": "stream_token", "token": "", "blocked": true, "action": "block_glba_ssn_leakage", "reason": "Statutory violation: Social Security Number (GLBA / FCRA) detected"}
→ {"type": "stream_chunk", "token": " Patient in room 402."}
← {"type": "error", "code": "STREAM_BLOCKED", "message": "The stream was blocked by a fatal invariant; send stream_start to begin a new stream.", "action": "block_glba_ssn_leakage", "reason": "Statutory violation: Social Security Number (GLBA / FCRA) detected"}
→ {"type": "stream_start"}
← {"type": "stream_started", "lookahead_chars": 28}