Skip to main content

HTTP API Reference

Complete reference for the Constellation Engine REST API, including all endpoints, request/response schemas, authentication, security, and configuration.

Base URL and Configuration

http://localhost:8080

The server port is configurable via the CONSTELLATION_PORT environment variable (default: 8080).

Quick Start

import io.constellation.http.ConstellationServer

// Minimal server (no hardening)
ConstellationServer
.builder(constellation, compiler)
.withPort(8080)
.run

// Production server with full hardening
ConstellationServer
.builder(constellation, compiler)
.withAuth(AuthConfig(apiKeys = Map("key1" -> ApiRole.Admin)))
.withCors(CorsConfig(allowedOrigins = Set("https://app.example.com")))
.withRateLimit(RateLimitConfig(requestsPerMinute = 100, burst = 20))
.withHealthChecks(HealthCheckConfig(enableDetailEndpoint = true))
.run

API Endpoints Summary

Core Operations

EndpointMethodDescriptionAuth Required
/compilePOSTCompile constellation-lang source codeYes
/executePOSTExecute a stored pipeline by referenceYes
/runPOSTCompile and execute in one requestYes
/modulesGETList all registered modulesYes
/namespacesGETList all function namespacesYes
/namespaces/{namespace}GETList functions in a namespaceYes

Health and Monitoring

EndpointMethodDescriptionAuth Required
/healthGETBasic health check (compatibility)No
/health/liveGETLiveness probe (always 200)No
/health/readyGETReadiness probe (checks lifecycle)No
/health/detailGETDetailed diagnostics (opt-in)Configurable
/metricsGETRuntime metrics (cache, scheduler, uptime)No

Pipeline Management

EndpointMethodDescriptionAuth Required
/pipelinesGETList all stored pipelinesYes
/pipelines/{ref}GETGet pipeline metadata by name or hashYes
/pipelines/{ref}DELETEDelete a stored pipelineYes
/pipelines/{name}/aliasPUTRepoint an alias to a different hashYes

Suspension Management

EndpointMethodDescriptionAuth Required
/executionsGETList suspended executionsYes
/executions/{id}GETGet suspension detailYes
/executions/{id}/resumePOSTResume with additional inputsYes
/executions/{id}DELETEDelete a suspended executionYes

Pipeline Versioning

EndpointMethodDescriptionAuth Required
/pipelines/{name}/reloadPOSTHot-reload a pipeline from new sourceYes
/pipelines/{name}/versionsGETList version historyYes
/pipelines/{name}/rollbackPOSTRollback to previous versionYes
/pipelines/{name}/rollback/{version}POSTRollback to specific versionYes

Canary Releases

EndpointMethodDescriptionAuth Required
/pipelines/{name}/canaryGETGet canary deployment status and metricsYes
/pipelines/{name}/canary/promotePOSTManually promote canary to next stepYes
/pipelines/{name}/canary/rollbackPOSTRollback canary deploymentYes
/pipelines/{name}/canaryDELETEAbort canary deploymentYes

Module HTTP Endpoints

EndpointMethodDescriptionAuth Required
/modules/publishedGETList modules published as HTTP endpointsYes
/modules/{name}/invokePOSTInvoke a published module directlyYes

WebSocket

EndpointProtocolDescriptionAuth Required
/lspWebSocketLanguage Server Protocol for IDE integrationNo

Core Operations

POST /compile

Compile constellation-lang source code into an executable pipeline without running it. Returns structural and syntactic hashes for content-addressed storage.

Request

Content-Type: application/json

{
"source": "in text: String\nresult = Uppercase(text)\nout result",
"name": "my-pipeline"
}

Fields:

FieldTypeRequiredDescription
sourceStringYesConstellation-lang source code
nameStringNoPipeline name (creates an alias)
dagNameStringNoDeprecated, use name instead

Response

Success (200 OK):

{
"success": true,
"structuralHash": "a1b2c3d4e5f6...",
"syntacticHash": "f6e5d4c3b2a1...",
"name": "my-pipeline"
}

Compilation Error (400 Bad Request):

{
"success": false,
"errors": [
"Line 3: Unknown module 'InvalidModule'",
"Line 5: Type mismatch: expected String, got Int"
]
}

Timeout (500 Internal Server Error):

Compilation times out after 30 seconds. Consider breaking large pipelines into smaller modules.

Example

curl -X POST http://localhost:8080/compile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"source": "in x: Int\nin y: Int\nresult = Add(x, y)\nout result",
"name": "add-pipeline"
}'

POST /execute

Execute a previously compiled pipeline by reference (name or structural hash). Pipelines are retrieved from the content-addressed PipelineStore.

Request

Content-Type: application/json

{
"ref": "my-pipeline",
"inputs": {
"text": "hello world"
}
}

Fields:

FieldTypeRequiredDescription
refStringYesPipeline name, structural hash, or sha256:<hash>
inputsObjectNoInput values keyed by variable name
dagNameStringNoDeprecated, use ref instead

Pipeline References:

  • Name: "my-pipeline" — resolves via alias
  • Hash (64 hex chars): "a1b2c3d4..." — treated as structural hash
  • SHA-256 prefix: "sha256:a1b2c3..." — explicit hash reference

Response

Success (200 OK):

{
"success": true,
"status": "completed",
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"outputs": {
"result": "HELLO WORLD"
},
"resumptionCount": 0
}

Suspended (200 OK):

When inputs are incomplete, execution suspends instead of failing:

{
"success": true,
"status": "suspended",
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"outputs": {},
"missingInputs": {
"count": "CInt",
"threshold": "CFloat"
},
"pendingOutputs": ["result", "summary"],
"resumptionCount": 0
}

Resume the execution using POST /executions/{executionId}/resume.

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline 'my-pipeline' not found",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Input Error (400 Bad Request):

{
"success": false,
"error": "Input error: Type mismatch for 'count': expected Int, got String"
}

Execution Failed (200 OK):

{
"success": false,
"status": "failed",
"error": "Module 'Divide' failed: Division by zero",
"outputs": {}
}

Overloaded Server (429 Too Many Requests):

{
"error": "QueueFull",
"message": "Server is overloaded, try again later",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Shutting Down (503 Service Unavailable):

{
"error": "ShuttingDown",
"message": "Server is shutting down",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Example

# Execute by name
curl -X POST http://localhost:8080/execute \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"ref": "add-pipeline",
"inputs": {"x": 10, "y": 32}
}'

# Execute by hash
curl -X POST http://localhost:8080/execute \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"ref": "a1b2c3d4e5f6...",
"inputs": {"x": 10, "y": 32}
}'

POST /run

Compile and execute constellation-lang source in a single request. Combines /compile and /execute for ad-hoc execution.

Use when: You have the source code and inputs ready, and don't need to store the pipeline for reuse.

Don't use when: You're executing the same pipeline repeatedly with different inputs (use /compile once, then /execute multiple times for better performance).

Request

Content-Type: application/json

{
"source": "in text: String\nresult = Uppercase(text)\nout result",
"inputs": {
"text": "hello world"
}
}

Fields:

FieldTypeRequiredDescription
sourceStringYesConstellation-lang source code
inputsObjectYesInput values keyed by variable name

Response

Success (200 OK):

{
"success": true,
"status": "completed",
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"structuralHash": "a1b2c3d4e5f6...",
"outputs": {
"result": "HELLO WORLD"
},
"resumptionCount": 0
}

Compilation Error (400 Bad Request):

{
"success": false,
"compilationErrors": [
"Line 2: Unknown module 'InvalidModule'"
]
}

Suspended (200 OK):

{
"success": true,
"status": "suspended",
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"structuralHash": "a1b2c3d4e5f6...",
"outputs": {},
"missingInputs": {
"threshold": "CFloat"
},
"pendingOutputs": ["result"],
"resumptionCount": 0
}

Example

curl -X POST http://localhost:8080/run \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"source": "in x: Int\nin y: Int\nsum = Add(x, y)\nout sum",
"inputs": {"x": 10, "y": 32}
}'

GET /modules

List all registered modules with their type signatures and descriptions.

Response

Success (200 OK):

{
"modules": [
{
"name": "Uppercase",
"description": "Convert text to uppercase",
"version": "1.0",
"inputs": {
"text": "CString"
},
"outputs": {
"result": "CString"
}
},
{
"name": "Add",
"description": "Add two integers",
"version": "1.0",
"inputs": {
"a": "CInt",
"b": "CInt"
},
"outputs": {
"result": "CInt"
}
}
]
}

Example

curl http://localhost:8080/modules \
-H "Authorization: Bearer your-api-key"

GET /namespaces

List all available function namespaces in the registry.

Response

Success (200 OK):

{
"namespaces": [
"text",
"data",
"math",
"stdlib"
]
}

Example

curl http://localhost:8080/namespaces \
-H "Authorization: Bearer your-api-key"

GET /namespaces/{namespace}

List all functions in a specific namespace.

Response

Success (200 OK):

{
"namespace": "text",
"functions": [
{
"name": "Uppercase",
"qualifiedName": "text.Uppercase",
"params": ["text: CString"],
"returns": "CString"
},
{
"name": "Trim",
"qualifiedName": "text.Trim",
"params": ["text: CString"],
"returns": "CString"
}
]
}

Namespace Not Found (404 Not Found):

{
"error": "NamespaceNotFound",
"message": "Namespace 'invalid' not found or has no functions"
}

Example

curl http://localhost:8080/namespaces/text \
-H "Authorization: Bearer your-api-key"

Health and Monitoring

GET /health/live

Liveness probe for Kubernetes and load balancers. Always returns 200 OK if the server process is running.

Response

Success (200 OK):

{
"status": "alive"
}

Example

curl http://localhost:8080/health/live

GET /health/ready

Readiness probe for Kubernetes and load balancers. Returns 200 OK when the server is ready to accept traffic, 503 Service Unavailable during graceful shutdown or startup.

Response

Ready (200 OK):

{
"status": "ready"
}

Not Ready (503 Service Unavailable):

{
"status": "not_ready"
}

Returns not ready when:

  • Server is in graceful shutdown (draining)
  • Custom readiness checks fail
  • Lifecycle state is not Running

Example

curl http://localhost:8080/health/ready

GET /health/detail

Detailed health diagnostics including lifecycle state, cache statistics, scheduler metrics, and custom readiness checks.

Opt-in: Must be enabled via HealthCheckConfig(enableDetailEndpoint = true).

Auth-gated by default: Set detailRequiresAuth = false to make this endpoint public.

Response

Success (200 OK):

{
"timestamp": "2026-02-09T12:34:56.789Z",
"lifecycle": {
"state": "Running"
},
"cache": {
"hits": 12543,
"misses": 437,
"hitRate": 0.9663,
"evictions": 12,
"entries": 128
},
"scheduler": {
"activeCount": 3,
"queuedCount": 15,
"totalSubmitted": 9876,
"totalCompleted": 9858
},
"readinessChecks": {
"database": true,
"external-api": true
}
}

Field Descriptions:

FieldDescription
timestampISO-8601 timestamp of the health check
lifecycle.stateServer lifecycle state: Running, Draining, or Stopped
cache.hitsTotal cache hits since startup
cache.missesTotal cache misses since startup
cache.hitRateHit rate as a fraction (0.0 to 1.0)
cache.evictionsNumber of cache evictions
cache.entriesCurrent number of cached entries
scheduler.activeCountCurrently executing tasks
scheduler.queuedCountTasks waiting in queue
scheduler.totalSubmittedTotal tasks submitted since startup
scheduler.totalCompletedTotal tasks completed since startup
readinessChecksResults of custom readiness checks (name → boolean)

Example

curl http://localhost:8080/health/detail \
-H "Authorization: Bearer your-api-key"

GET /metrics

Runtime metrics for monitoring and observability. Supports content negotiation:

  • JSON: Default format (or Accept: application/json)
  • Prometheus: Use Accept: text/plain for Prometheus-compatible format

Response (JSON)

Success (200 OK):

{
"timestamp": "2026-02-09T12:34:56.789Z",
"server": {
"uptime_seconds": 86400,
"requests_total": 123456
},
"cache": {
"hits": 12543,
"misses": 437,
"hitRate": 0.9663,
"evictions": 12,
"entries": 128
},
"scheduler": {
"enabled": true,
"activeCount": 3,
"queuedCount": 15,
"totalSubmitted": 9876,
"totalCompleted": 9858,
"highPriorityCompleted": 5432,
"lowPriorityCompleted": 4426,
"starvationPromotions": 18
}
}

Response (Prometheus)

Success (200 OK), Content-Type: text/plain:

# HELP constellation_server_uptime_seconds Server uptime in seconds
# TYPE constellation_server_uptime_seconds counter
constellation_server_uptime_seconds 86400

# HELP constellation_requests_total Total number of requests handled
# TYPE constellation_requests_total counter
constellation_requests_total 123456

# HELP constellation_cache_hits_total Total cache hits
# TYPE constellation_cache_hits_total counter
constellation_cache_hits_total 12543

# HELP constellation_cache_misses_total Total cache misses
# TYPE constellation_cache_misses_total counter
constellation_cache_misses_total 437

# HELP constellation_cache_hit_rate Cache hit rate
# TYPE constellation_cache_hit_rate gauge
constellation_cache_hit_rate 0.9663

# HELP constellation_cache_entries Current number of cached entries
# TYPE constellation_cache_entries gauge
constellation_cache_entries 128

Example

# JSON format
curl http://localhost:8080/metrics

# Prometheus format
curl http://localhost:8080/metrics \
-H "Accept: text/plain"

Module HTTP Endpoints

GET /modules/published

List all modules that have been published as HTTP endpoints via ModuleBuilder.httpEndpoint().

Response

Success (200 OK):

{
"modules": [
{
"name": "Uppercase",
"description": "Convert text to uppercase",
"version": "1.0",
"tags": ["text"],
"endpoint": "/modules/Uppercase/invoke",
"inputs": {
"text": "CString"
},
"outputs": {
"result": "CString"
}
}
]
}

Fields:

FieldTypeDescription
nameStringModule name
descriptionStringModule description
versionStringModule version (major.minor)
tagsArray[String]Module tags
endpointStringInvocation endpoint path
inputsObjectInput field names and their CType
outputsObjectOutput field names and their CType

Example

curl http://localhost:8080/modules/published \
-H "Authorization: Bearer your-api-key"

POST /modules/{name}/invoke

Invoke a published module directly with JSON inputs. Builds a synthetic single-node DAG and delegates to Runtime.run(), reusing all existing execution infrastructure.

Request

Content-Type: application/json

{
"text": "hello world"
}

The request body is a flat JSON object where keys match the module's consumes fields.

Response

Success (200 OK):

{
"success": true,
"outputs": {
"result": "HELLO WORLD"
},
"module": "Uppercase"
}

Module Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Module 'Unknown' not found"
}

Module Not Published (404 Not Found):

{
"error": "NotFound",
"message": "Module 'Secret' is not published as an HTTP endpoint"
}

Input Error (400 Bad Request):

{
"success": false,
"error": "Missing required input: 'text'",
"module": "Uppercase"
}

Execution Error (500 Internal Server Error):

{
"success": false,
"error": "Module execution failed",
"module": "Uppercase"
}

Example

curl -X POST http://localhost:8080/modules/Uppercase/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{"text": "hello world"}'

Pipeline Management

GET /pipelines

List all stored pipeline images with their metadata.

Response

Success (200 OK):

{
"pipelines": [
{
"structuralHash": "a1b2c3d4e5f6...",
"syntacticHash": "f6e5d4c3b2a1...",
"aliases": ["my-pipeline", "prod-pipeline"],
"compiledAt": "2026-02-09T10:15:30.456Z",
"moduleCount": 5,
"declaredOutputs": ["result", "summary"]
}
]
}

Example

curl http://localhost:8080/pipelines \
-H "Authorization: Bearer your-api-key"

GET /pipelines/{ref}

Get detailed metadata for a specific pipeline by name or structural hash.

Response

Success (200 OK):

{
"structuralHash": "a1b2c3d4e5f6...",
"syntacticHash": "f6e5d4c3b2a1...",
"aliases": ["my-pipeline"],
"compiledAt": "2026-02-09T10:15:30.456Z",
"declaredOutputs": ["result"],
"inputSchema": {
"text": "CString",
"count": "CInt"
},
"outputSchema": {
"result": "CString"
},
"modules": [
{
"name": "Uppercase",
"description": "Convert text to uppercase",
"version": "1.0",
"inputs": {"text": "CString"},
"outputs": {"result": "CString"}
}
]
}

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline 'my-pipeline' not found"
}

Example

# By name
curl http://localhost:8080/pipelines/my-pipeline \
-H "Authorization: Bearer your-api-key"

# By hash
curl http://localhost:8080/pipelines/a1b2c3d4e5f6... \
-H "Authorization: Bearer your-api-key"

DELETE /pipelines/{ref}

Delete a stored pipeline by name or structural hash. Fails if other aliases still point to the same structural hash.

Response

Success (200 OK):

{
"deleted": true
}

Alias Conflict (409 Conflict):

{
"error": "AliasConflict",
"message": "Cannot delete pipeline: aliases [prod-pipeline, backup-pipeline] point to it"
}

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline 'my-pipeline' not found"
}

Example

curl -X DELETE http://localhost:8080/pipelines/my-pipeline \
-H "Authorization: Bearer your-api-key"

PUT /pipelines/{name}/alias

Repoint an alias to a different structural hash. Creates the alias if it doesn't exist.

Request

Content-Type: application/json

{
"structuralHash": "a1b2c3d4e5f6..."
}

Response

Success (200 OK):

{
"name": "my-pipeline",
"structuralHash": "a1b2c3d4e5f6..."
}

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline with hash 'a1b2c3d4e5f6...' not found"
}

Example

curl -X PUT http://localhost:8080/pipelines/my-pipeline/alias \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{"structuralHash": "a1b2c3d4e5f6..."}'

Suspension Management

GET /executions

List all suspended executions. Requires suspension store to be configured.

Response

Success (200 OK):

{
"executions": [
{
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"structuralHash": "a1b2c3d4e5f6...",
"resumptionCount": 2,
"missingInputs": {
"threshold": "CFloat",
"maxRetries": "CInt"
},
"createdAt": "2026-02-09T10:15:30.456Z"
}
]
}

No Suspension Store (200 OK):

{
"executions": []
}

Example

curl http://localhost:8080/executions \
-H "Authorization: Bearer your-api-key"

GET /executions/{id}

Get detailed information about a suspended execution.

Response

Success (200 OK):

{
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"structuralHash": "a1b2c3d4e5f6...",
"resumptionCount": 2,
"missingInputs": {
"threshold": "CFloat",
"maxRetries": "CInt"
},
"createdAt": "2026-02-09T10:15:30.456Z"
}

Execution Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Execution '550e8400-e29b-41d4-a716-446655440000' not found"
}

Example

curl http://localhost:8080/executions/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer your-api-key"

POST /executions/{id}/resume

Resume a suspended execution by providing missing inputs or manually resolved node values.

Request

Content-Type: application/json

{
"additionalInputs": {
"threshold": 0.95,
"maxRetries": 3
},
"resolvedNodes": {
"computed_value": "override"
}
}

Fields:

FieldTypeRequiredDescription
additionalInputsObjectNoNew input values keyed by variable name
resolvedNodesObjectNoManually-resolved data node values keyed by variable name

Response

Success (200 OK):

Same format as /execute response. May complete or suspend again.

{
"success": true,
"status": "completed",
"executionId": "550e8400-e29b-41d4-a716-446655440000",
"outputs": {
"result": "final output"
},
"resumptionCount": 3
}

Resume In Progress (409 Conflict):

{
"error": "ResumeInProgress",
"message": "A resume operation is already in progress for execution '550e8400-e29b-41d4-a716-446655440000'",
"requestId": "550e8400-e29b-41d4-a716-446655440001"
}

Input Error (400 Bad Request):

{
"success": false,
"error": "Input error: Type mismatch for 'threshold': expected Float, got String"
}

Execution Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Execution '550e8400-e29b-41d4-a716-446655440000' not found",
"requestId": "550e8400-e29b-41d4-a716-446655440001"
}

Example

curl -X POST http://localhost:8080/executions/550e8400-e29b-41d4-a716-446655440000/resume \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"additionalInputs": {
"threshold": 0.95,
"maxRetries": 3
}
}'

DELETE /executions/{id}

Delete a suspended execution from the suspension store.

Response

Success (200 OK):

{
"deleted": true
}

Execution Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Execution '550e8400-e29b-41d4-a716-446655440000' not found"
}

Example

curl -X DELETE http://localhost:8080/executions/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer your-api-key"

Pipeline Versioning

Pipeline versioning must be enabled on the server (via PipelineVersionStore). These endpoints manage the version history of named pipelines.

POST /pipelines/{name}/reload

Hot-reload a named pipeline from new source code. Compiles the source, stores the new image, and atomically updates the alias (unless starting a canary deployment).

Request

Content-Type: application/json

{
"source": "in text: String\nresult = Lowercase(text)\nout result"
}

With Canary:

{
"source": "in text: String\nresult = Lowercase(text)\nout result",
"canary": {
"initialWeight": 0.05,
"promotionSteps": [0.10, 0.25, 0.50, 1.0],
"observationWindow": "5m",
"errorThreshold": 0.05,
"autoPromote": true
}
}

Fields:

FieldTypeRequiredDescription
sourceStringNoNew source code. If omitted, re-reads from file (if path known)
canaryObjectNoCanary deployment configuration
canary.initialWeightFloatNoInitial traffic percentage (0.0 to 1.0), default: 0.05
canary.promotionStepsArray[Float]NoTraffic percentages for each promotion step
canary.observationWindowStringNoDuration to observe metrics before auto-promotion (e.g. "5m")
canary.errorThresholdFloatNoMax error rate before rollback (0.0 to 1.0)
canary.latencyThresholdMsLongNoMax p99 latency before rollback (milliseconds)
canary.minRequestsIntNoMinimum requests before promotion
canary.autoPromoteBooleanNoEnable automatic promotion, default: false

Response

Success (200 OK):

{
"success": true,
"previousHash": "f6e5d4c3b2a1...",
"newHash": "a1b2c3d4e5f6...",
"name": "my-pipeline",
"changed": true,
"version": 3
}

Unchanged (200 OK):

If the structural hash didn't change (no semantic changes):

{
"success": true,
"previousHash": "a1b2c3d4e5f6...",
"newHash": "a1b2c3d4e5f6...",
"name": "my-pipeline",
"changed": false,
"version": 2
}

With Canary Started (200 OK):

{
"success": true,
"previousHash": "f6e5d4c3b2a1...",
"newHash": "a1b2c3d4e5f6...",
"name": "my-pipeline",
"changed": true,
"version": 3,
"canary": {
"pipelineName": "my-pipeline",
"oldVersion": {"version": 2, "structuralHash": "f6e5d4c3b2a1..."},
"newVersion": {"version": 3, "structuralHash": "a1b2c3d4e5f6..."},
"currentWeight": 0.05,
"currentStep": 0,
"status": "observing",
"startedAt": "2026-02-09T12:00:00.000Z",
"metrics": {
"oldVersion": {"requests": 0, "successes": 0, "failures": 0, "avgLatencyMs": 0.0, "p99LatencyMs": 0.0},
"newVersion": {"requests": 0, "successes": 0, "failures": 0, "avgLatencyMs": 0.0, "p99LatencyMs": 0.0}
}
}
}

Compilation Error (400 Bad Request):

{
"error": "CompilationError",
"message": "Line 2: Unknown module 'InvalidModule'"
}

No Source (400 Bad Request):

{
"error": "NoSource",
"message": "No source provided and no file path known for this pipeline"
}

Canary Conflict (409 Conflict):

{
"error": "CanaryConflict",
"message": "No previous version exists for canary deployment"
}

Example

# Simple reload
curl -X POST http://localhost:8080/pipelines/my-pipeline/reload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"source": "in text: String\nresult = Lowercase(text)\nout result"
}'

# Reload with canary
curl -X POST http://localhost:8080/pipelines/my-pipeline/reload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"source": "in text: String\nresult = Lowercase(text)\nout result",
"canary": {
"initialWeight": 0.05,
"promotionSteps": [0.10, 0.25, 0.50, 1.0],
"observationWindow": "5m",
"errorThreshold": 0.05,
"autoPromote": true
}
}'

GET /pipelines/{name}/versions

List version history for a named pipeline.

Response

Success (200 OK):

{
"name": "my-pipeline",
"activeVersion": 3,
"versions": [
{
"version": 1,
"structuralHash": "f6e5d4c3b2a1...",
"createdAt": "2026-02-08T10:00:00.000Z",
"active": false
},
{
"version": 2,
"structuralHash": "a1b2c3d4e5f6...",
"createdAt": "2026-02-08T14:30:00.000Z",
"active": false
},
{
"version": 3,
"structuralHash": "b2c3d4e5f6a1...",
"createdAt": "2026-02-09T09:15:00.000Z",
"active": true
}
]
}

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline 'my-pipeline' not found"
}

Versioning Not Enabled (400 Bad Request):

{
"error": "VersioningNotEnabled",
"message": "Versioning not enabled"
}

Example

curl http://localhost:8080/pipelines/my-pipeline/versions \
-H "Authorization: Bearer your-api-key"

POST /pipelines/{name}/rollback

Rollback to the previous version of a pipeline. Atomically updates the alias to point to the previous structural hash.

Response

Success (200 OK):

{
"success": true,
"name": "my-pipeline",
"previousVersion": 3,
"activeVersion": 2,
"structuralHash": "a1b2c3d4e5f6..."
}

No Previous Version (404 Not Found):

{
"error": "NotFound",
"message": "No previous version exists for pipeline 'my-pipeline'"
}

Pipeline Not Found (404 Not Found):

{
"error": "NotFound",
"message": "Pipeline 'my-pipeline' not found"
}

Versioning Not Enabled (400 Bad Request):

{
"error": "VersioningNotEnabled",
"message": "Versioning not enabled"
}

Example

curl -X POST http://localhost:8080/pipelines/my-pipeline/rollback \
-H "Authorization: Bearer your-api-key"

POST /pipelines/{name}/rollback/{version}

Rollback to a specific version of a pipeline.

Response

Same as /rollback endpoint.

Example

curl -X POST http://localhost:8080/pipelines/my-pipeline/rollback/2 \
-H "Authorization: Bearer your-api-key"

Canary Releases

Canary deployments split traffic between an old and new pipeline version, gradually promoting the new version if metrics are healthy.

GET /pipelines/{name}/canary

Get the current canary deployment status and metrics for a pipeline.

Response

Success (200 OK):

{
"pipelineName": "my-pipeline",
"oldVersion": {
"version": 2,
"structuralHash": "f6e5d4c3b2a1..."
},
"newVersion": {
"version": 3,
"structuralHash": "a1b2c3d4e5f6..."
},
"currentWeight": 0.25,
"currentStep": 2,
"status": "observing",
"startedAt": "2026-02-09T12:00:00.000Z",
"metrics": {
"oldVersion": {
"requests": 750,
"successes": 745,
"failures": 5,
"avgLatencyMs": 123.4,
"p99LatencyMs": 456.7
},
"newVersion": {
"requests": 250,
"successes": 248,
"failures": 2,
"avgLatencyMs": 115.2,
"p99LatencyMs": 432.1
}
}
}

Canary Status Values:

StatusDescription
observingCollecting metrics during observation window
promotingPromotion in progress (transitional)
rolled_backCanary rolled back due to failed health checks
completeCanary completed successfully, 100% on new version

No Canary (404 Not Found):

{
"error": "NotFound",
"message": "No canary deployment active for pipeline 'my-pipeline'"
}

Canary Not Enabled (400 Bad Request):

{
"error": "CanaryNotEnabled",
"message": "Canary routing not enabled"
}

Example

curl http://localhost:8080/pipelines/my-pipeline/canary \
-H "Authorization: Bearer your-api-key"

POST /pipelines/{name}/canary/promote

Manually advance the canary deployment to the next promotion step.

Response

Success (200 OK):

Returns updated canary state (same format as GET /canary).

No Canary (404 Not Found):

{
"error": "NotFound",
"message": "No canary deployment active for pipeline 'my-pipeline'"
}

Canary Not Enabled (400 Bad Request):

{
"error": "CanaryNotEnabled",
"message": "Canary routing not enabled"
}

Example

curl -X POST http://localhost:8080/pipelines/my-pipeline/canary/promote \
-H "Authorization: Bearer your-api-key"

POST /pipelines/{name}/canary/rollback

Rollback the canary deployment, routing all traffic back to the old version.

Response

Success (200 OK):

Returns updated canary state with status: "rolled_back".

No Canary (404 Not Found):

{
"error": "NotFound",
"message": "No canary deployment active for pipeline 'my-pipeline'"
}

Example

curl -X POST http://localhost:8080/pipelines/my-pipeline/canary/rollback \
-H "Authorization: Bearer your-api-key"

DELETE /pipelines/{name}/canary

Abort the canary deployment and route all traffic back to the old version.

Response

Success (200 OK):

Returns updated canary state with status: "rolled_back".

No Canary (404 Not Found):

{
"error": "NotFound",
"message": "No canary deployment active for pipeline 'my-pipeline'"
}

Example

curl -X DELETE http://localhost:8080/pipelines/my-pipeline/canary \
-H "Authorization: Bearer your-api-key"

Authentication

Authentication is opt-in via AuthConfig. When enabled, all requests except public paths must include an API key in the Authorization header.

Configuring Authentication

Environment Variable:

export CONSTELLATION_API_KEYS="admin-key-123:Admin,exec-key-456:Execute,read-key-789:ReadOnly"

Programmatic:

val authConfig = AuthConfig(apiKeys = Map(
"admin-key-123" -> ApiRole.Admin,
"exec-key-456" -> ApiRole.Execute,
"read-key-789" -> ApiRole.ReadOnly
))

ConstellationServer
.builder(constellation, compiler)
.withAuth(authConfig)
.run

API Key Requirements

  • Minimum length: 24 characters
  • No control characters: Must be printable ASCII/UTF-8
  • Format: key:Role where Role is Admin, Execute, or ReadOnly (case-insensitive)

Roles and Permissions

RoleHTTP MethodsUse Case
AdminGET, POST, PUT, DELETE, PATCHFull access to all endpoints
ExecuteGET, POSTExecute pipelines, read metadata (no deletion)
ReadOnlyGETRead-only access (monitoring, inspection)

Public Paths

These paths are always accessible without authentication:

  • /health
  • /health/live
  • /health/ready
  • /metrics

Using API Keys

Request Header:

Authorization: Bearer your-api-key

Example:

curl http://localhost:8080/modules \
-H "Authorization: Bearer admin-key-123"

Error Responses

Missing Authorization (401 Unauthorized):

{
"error": "Unauthorized",
"message": "Missing or invalid Authorization header. Expected: Bearer <api-key>"
}

Invalid API Key (401 Unauthorized):

{
"error": "Unauthorized",
"message": "Invalid API key"
}

Insufficient Permissions (403 Forbidden):

{
"error": "Forbidden",
"message": "Role 'ReadOnly' does not permit POST requests"
}

Security Best Practices

  1. Use strong keys: Generate random keys with at least 32 characters
  2. Rotate keys regularly: Update keys periodically via environment variables
  3. Use HTTPS in production: API keys are transmitted in headers (use TLS)
  4. Don't commit keys: Store keys in secrets management systems (Vault, AWS Secrets Manager)
  5. Log access: Monitor authentication failures for suspicious activity

CORS Configuration

Cross-Origin Resource Sharing (CORS) is opt-in via CorsConfig. When enabled, the server sends appropriate CORS headers to allow browser-based clients.

Configuring CORS

Environment Variable:

export CONSTELLATION_CORS_ORIGINS="https://app.example.com,https://admin.example.com"

Programmatic:

val corsConfig = CorsConfig(
allowedOrigins = Set("https://app.example.com", "https://admin.example.com"),
allowedMethods = Set("GET", "POST", "PUT", "DELETE", "OPTIONS"),
allowedHeaders = Set("Content-Type", "Authorization"),
allowCredentials = false,
maxAge = 3600
)

ConstellationServer
.builder(constellation, compiler)
.withCors(corsConfig)
.run

CORS Options

FieldTypeDefaultDescription
allowedOriginsSet[String]EmptyAllowed origin URLs (e.g. https://app.example.com)
allowedMethodsSet[String]GET, POST, PUT, DELETE, OPTIONSHTTP methods allowed in CORS requests
allowedHeadersSet[String]Content-Type, AuthorizationHeaders the client may send
allowCredentialsBooleanfalseWhether to include credentials (cookies, auth headers)
maxAgeLong3600Preflight cache duration in seconds

Wildcard Origins

Development only:

export CONSTELLATION_CORS_ORIGINS="*"

Note: Cannot combine allowCredentials = true with wildcard origins.

Origin Validation

  • HTTPS required: Non-localhost origins must use HTTPS
  • Localhost exception: http://localhost, http://127.0.0.1, http://[::1] are allowed
  • Malformed URLs rejected: Invalid origin formats are logged and skipped

CORS Headers

When CORS is enabled, the server sends:

  • Access-Control-Allow-Origin: <origin>
  • Access-Control-Allow-Methods: GET, POST, ...
  • Access-Control-Allow-Headers: Content-Type, Authorization, ...
  • Access-Control-Max-Age: 3600
  • Access-Control-Allow-Credentials: true (if enabled)

Rate Limiting

Rate limiting is opt-in via RateLimitConfig. When enabled, the server enforces per-IP and per-API-key rate limits using token bucket algorithm.

Configuring Rate Limiting

Environment Variables:

export CONSTELLATION_RATE_LIMIT_RPM=100      # Requests per minute per IP
export CONSTELLATION_RATE_LIMIT_BURST=20 # Burst size (token bucket capacity)

Programmatic:

val rateLimitConfig = RateLimitConfig(
requestsPerMinute = 100,
burst = 20,
keyRequestsPerMinute = 200,
keyBurst = 40
)

ConstellationServer
.builder(constellation, compiler)
.withRateLimit(rateLimitConfig)
.run

Rate Limit Options

FieldTypeDefaultDescription
requestsPerMinuteInt100Sustained requests per minute per IP
burstInt20Maximum burst size (token bucket capacity)
keyRequestsPerMinuteInt200Sustained requests per minute per API key
keyBurstInt40Maximum burst size for API keys
exemptPathsSet[String]/health, /health/live, /health/ready, /metricsPaths exempt from rate limiting

Two-Layer Rate Limiting

Rate limiting is applied in two layers:

  1. Per-IP: Every client IP is rate-limited independently
  2. Per-API-key: Authenticated clients are also rate-limited by their API key

A request must pass both checks. This prevents:

  • A single API key from monopolizing an IP's budget
  • Multiple keys on the same IP from bypassing the IP limit

Exempt Paths

Public monitoring endpoints bypass rate limiting (prefix match):

  • /health
  • /health/live
  • /health/ready
  • /metrics

Error Response

Rate Limit Exceeded (429 Too Many Requests):

{
"error": "RateLimitExceeded",
"message": "Too many requests, please try again later"
}

Headers:

Retry-After: 60

Token Bucket Algorithm

  • Tokens: Each request consumes 1 token
  • Refill rate: Tokens refill at requestsPerMinute / 60 per second
  • Capacity: Token bucket holds up to burst tokens
  • Non-blocking: Uses tryAcquire (no waiting, immediate 429 response)

Best Practices

  1. Set burst > RPM/60: Allow short bursts above sustained rate
  2. Monitor 429 responses: Track rate limit rejections in metrics
  3. Use exponential backoff: Clients should back off when hitting limits
  4. Adjust per workload: Higher limits for batch processing, lower for interactive

Error Responses

All error responses follow a consistent JSON format:

{
"error": "ErrorCode",
"message": "Human-readable error description",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Common Error Codes

HTTP StatusError CodeDescription
400 Bad RequestInvalidRequestMalformed request body or parameters
400 Bad RequestCompilationErrorSource code failed to compile
400 Bad RequestInputErrorInput values don't match expected types
400 Bad RequestVersioningNotEnabledVersioning endpoints require server config
400 Bad RequestCanaryNotEnabledCanary endpoints require server config
401 UnauthorizedUnauthorizedMissing or invalid API key
403 ForbiddenForbiddenAPI key role doesn't permit this operation
404 Not FoundNotFoundResource (pipeline, execution, etc.) not found
404 Not FoundNamespaceNotFoundFunction namespace doesn't exist
409 ConflictAliasConflictCannot delete pipeline with active aliases
409 ConflictResumeInProgressConcurrent resume operation detected
409 ConflictCanaryConflictCanary deployment precondition failed
413 Payload Too LargePayloadTooLargeRequest body exceeds 10MB limit
429 Too Many RequestsRateLimitExceededRate limit exceeded, retry later
429 Too Many RequestsQueueFullServer overloaded, queue full
500 Internal Server ErrorInternalErrorUnexpected server error
503 Service UnavailableShuttingDownServer in graceful shutdown

Request ID

All error responses include a requestId field for tracing. The ID is either:

  • Extracted from X-Request-ID request header (if provided)
  • Generated as a random UUID (if not provided)

Use request IDs to correlate logs and troubleshoot issues.


Configuration Reference

Environment Variables

VariableTypeDefaultDescription
CONSTELLATION_PORTInt8080HTTP server port
CONSTELLATION_API_KEYSString(none)Comma-separated key:Role pairs for authentication
CONSTELLATION_CORS_ORIGINSString(none)Comma-separated origin URLs for CORS
CONSTELLATION_RATE_LIMIT_RPMInt100Requests per minute per client IP
CONSTELLATION_RATE_LIMIT_BURSTInt20Burst size for rate limiter token bucket
CONSTELLATION_SCHEDULER_ENABLEDBooleanfalseEnable bounded scheduler with priority ordering
CONSTELLATION_SCHEDULER_MAX_CONCURRENCYInt16Maximum concurrent tasks when scheduler enabled
CONSTELLATION_SCHEDULER_STARVATION_TIMEOUTDuration30sTime before low-priority tasks get priority boost

Programmatic Configuration

Minimal Server:

import io.constellation.http.ConstellationServer

ConstellationServer
.builder(constellation, compiler)
.withPort(8080)
.run

Production Server:

import io.constellation.http.*
import java.nio.file.Paths

ConstellationServer
.builder(constellation, compiler)
.withHost("0.0.0.0")
.withPort(8080)
.withAuth(AuthConfig(apiKeys = Map(
"admin-key-123" -> ApiRole.Admin,
"exec-key-456" -> ApiRole.Execute
)))
.withCors(CorsConfig(allowedOrigins = Set("https://app.example.com")))
.withRateLimit(RateLimitConfig(requestsPerMinute = 100, burst = 20))
.withHealthChecks(HealthCheckConfig(
enableDetailEndpoint = true,
detailRequiresAuth = true
))
.withPipelineLoader(PipelineLoaderConfig(
directory = Paths.get("pipelines"),
watchForChanges = true
))
.withPersistentPipelineStore(Paths.get(".constellation-store"))
.run

Builder Methods

MethodParametersDescription
.withHost(host)StringServer bind address (default: "0.0.0.0")
.withPort(port)IntServer port (default: 8080)
.withAuth(config)AuthConfigEnable API key authentication
.withCors(config)CorsConfigEnable CORS headers
.withRateLimit(config)RateLimitConfigEnable per-IP and per-key rate limiting
.withHealthChecks(config)HealthCheckConfigConfigure health check endpoints
.withDashboardNoneEnable dashboard with default config
.withDashboard(config)DashboardConfigEnable dashboard with custom config
.withDashboard(dir)PathEnable dashboard with CST directory
.withPipelineLoader(config)PipelineLoaderConfigPre-load pipelines from directory on startup
.withPersistentPipelineStore(dir)PathEnable filesystem-backed pipeline store
.withExecutionWebSocket(ws)ExecutionWebSocketEnable WebSocket for live execution events

WebSocket Endpoints

LSP WebSocket (WS /lsp)

Language Server Protocol endpoint for IDE integration. Provides real-time diagnostics, autocomplete, and hover information.

Protocol: JSON-RPC 2.0 over WebSocket

See LSP WebSocket Documentation for full protocol details.

Example (JavaScript):

const ws = new WebSocket('ws://localhost:8080/lsp');

ws.onopen = () => {
// Initialize LSP
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
capabilities: {}
}
}));
};

ws.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log('LSP Response:', response);
};

Performance Considerations

Request Body Size Limit

Maximum request body size is 10MB (10,485,760 bytes). Requests exceeding this limit receive:

413 Payload Too Large:

{
"error": "PayloadTooLarge",
"message": "Request body too large: 12345678 bytes (max 10485760)"
}

Mitigation:

  • Break large pipelines into smaller modules
  • Stream large inputs via suspension/resume
  • Compress payloads before sending (gzip)

Compilation Timeout

Compilation operations timeout after 30 seconds. If compilation takes longer:

500 Internal Server Error:

{
"success": false,
"error": "Unexpected error: Compilation timed out after 30 seconds"
}

Mitigation:

  • Simplify complex pipelines
  • Use incremental compilation (pre-compile modules)
  • Increase timeout in server configuration (advanced)

Caching

The server uses CachingLangCompiler for compilation caching. Cache statistics are available via /metrics.

Target hit rate: > 80% for stable codebases

Cache key: SHA-256 hash of source code (content-addressed)

Idle Timeout

WebSocket connections timeout after 10 minutes of inactivity. Send periodic pings to keep connections alive.


Examples

Complete Workflow

# 1. Check server health
curl http://localhost:8080/health/ready

# 2. List available modules
curl http://localhost:8080/modules \
-H "Authorization: Bearer admin-key-123"

# 3. Compile a pipeline
PIPELINE_HASH=$(curl -s -X POST http://localhost:8080/compile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer admin-key-123" \
-d '{
"source": "in text: String\ncleaned = Trim(text)\nresult = Uppercase(cleaned)\nout result",
"name": "text-pipeline"
}' | jq -r '.structuralHash')

# 4. Execute the pipeline
curl -X POST http://localhost:8080/execute \
-H "Content-Type: application/json" \
-H "Authorization: Bearer admin-key-123" \
-d "{
\"ref\": \"$PIPELINE_HASH\",
\"inputs\": {\"text\": \" hello world \"}
}"

# 5. Check metrics
curl http://localhost:8080/metrics \
-H "Accept: text/plain"

Handling Suspensions

# 1. Execute with incomplete inputs
RESPONSE=$(curl -s -X POST http://localhost:8080/execute \
-H "Content-Type: application/json" \
-H "Authorization: Bearer admin-key-123" \
-d '{
"ref": "my-pipeline",
"inputs": {"text": "hello"}
}')

# 2. Check if suspended
STATUS=$(echo $RESPONSE | jq -r '.status')
if [ "$STATUS" = "suspended" ]; then
# 3. Extract execution ID and missing inputs
EXEC_ID=$(echo $RESPONSE | jq -r '.executionId')
echo "Execution suspended: $EXEC_ID"
echo "Missing inputs:" $(echo $RESPONSE | jq -r '.missingInputs')

# 4. Resume with additional inputs
curl -X POST "http://localhost:8080/executions/$EXEC_ID/resume" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer admin-key-123" \
-d '{
"additionalInputs": {
"count": 42,
"threshold": 0.95
}
}'
fi

Hot-Reload with Canary

# 1. Reload pipeline with canary deployment
curl -X POST http://localhost:8080/pipelines/my-pipeline/reload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer admin-key-123" \
-d '{
"source": "in text: String\nresult = Lowercase(text)\nout result",
"canary": {
"initialWeight": 0.05,
"promotionSteps": [0.10, 0.25, 0.50, 1.0],
"observationWindow": "5m",
"errorThreshold": 0.05,
"autoPromote": true
}
}'

# 2. Monitor canary metrics
watch -n 10 'curl -s http://localhost:8080/pipelines/my-pipeline/canary \
-H "Authorization: Bearer admin-key-123" | jq ".metrics"'

# 3. Manually promote if healthy
curl -X POST http://localhost:8080/pipelines/my-pipeline/canary/promote \
-H "Authorization: Bearer admin-key-123"

# 4. Or rollback if unhealthy
curl -X POST http://localhost:8080/pipelines/my-pipeline/canary/rollback \
-H "Authorization: Bearer admin-key-123"

See Also