Skip to content

Deployments

The Deployments API enables you to create, monitor, and control deployments programmatically. Use it to integrate Mantis with your CI/CD pipelines and automation workflows.

MethodEndpointDescription
GET/api/v1/deploymentsList all deployments (paginated)
POST/api/v1/deploymentsCreate a new deployment
POST/api/v1/deployments/previewPreview deployment plan (dry-run)
GET/api/v1/deployments/{id}Get deployment details
POST/api/v1/deployments/{id}/cancelCancel a running deployment
POST/api/v1/deployments/{id}/redeployRedeploy with same configuration
GET/api/v1/deployments/{id}/logsStream deployment logs (SSE)
GET/api/v1/deployments/{id}/logs/historyGet historical logs
GET/api/v1/deployments/{id}/analysisGet post-deployment analysis
GET/api/v1/deployments/pendingList queued deployments
DELETE/api/v1/deployments/pending/{id}Cancel a queued deployment

Retrieve a paginated list of deployments.

GET /api/v1/deployments?page=1&limit=50&tenant_id=<uuid>
Authorization: Bearer <token>
ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 50, max: 100)
tenant_idstring (UUID)NoFilter by tenant ID
{
"data": [
{
"id": "d9e8f7c6-b5a4-3210-9876-543210fedcba",
"source_type": "solution",
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"solution_name": "Web Application",
"solution_version": "2.1.0",
"sequence_id": null,
"sequence_name": null,
"sequence_version": null,
"action_id": null,
"action_name": null,
"action_version": null,
"state": "success",
"progress": 1.0,
"total_steps": 15,
"completed_steps": 15,
"failed_steps": 0,
"tenant_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"tenant_name": "Production",
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "jane.operator",
"created_at": "2024-01-20T14:30:00Z",
"started_at": "2024-01-20T14:30:00Z",
"completed_at": "2024-01-20T14:35:12Z",
"duration_ms": 312000
}
],
"meta": {
"total": 247,
"page": 1,
"limit": 50,
"total_pages": 5,
"has_next": true,
"has_prev": false
}
}

The source_type field indicates what the deployment runs. The matching *_id, *_name, and *_version fields are populated for that source; the other two source groups are null.

SourceDescription
solutionDeploys a full solution
sequenceDeploys a single sequence
actionDeploys a single action

The state field reflects the deployment lifecycle.

StateDescription
pendingDeployment created, not yet started
queuedAwaiting dispatch to a Thorax instance
runningCurrently executing
successCompleted successfully
failedCompleted with failures
cancelledUser-cancelled deployment
rolling_backRolling back after failure
rolled_backRollback completed

Create and start a new deployment.

POST /api/v1/deployments
Authorization: Bearer <token>
Content-Type: application/json
{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"override_freeze": false
}

You must supply at least one of target_ids (explicit UUIDs or SqIds) or target_tags (key-value pairs). Omitting both returns a 400 validation error.

Deploy to an explicit list of targets (UUIDs or SqIds):

{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_ids": ["t1-uuid", "t2-uuid", "t3-uuid"]
}
FieldTypeRequiredDescription
solution_idstringYes*Solution to deploy (UUID or SqId)
action_idstringYes*Action to execute (alternative to solution)
sequence_idstringYes*Sequence to execute (alternative to solution)
version_requirementstringNoVersion constraint for the source (e.g. ^1.0.0); latest if omitted
target_idsarray of stringNoSpecific target IDs or SqIds
target_tagsarray of objectNoTag filter — array of { key, value } pairs
target_tags_match_allbooleanNoRequire ALL target_tags to match (default: any)
target_modestring|objectNoExecution mode: sequential, parallel, or { "rolling": { "batch_size": N } }
failure_strategystringNostop_on_first_failure, continue_on_failure, or ignore_failures
rollback_strategystringNonone, failed_target_only, or all_executed
priorityintegerNoCommand priority, -100 to 100 (higher = more urgent)
timeout_secondsintegerNoPer-command timeout in seconds (1–86400)
max_retriesintegerNoMax retries per command (0–10)
deployment_timeout_secondsintegerNoOverall deployment timeout in seconds (0 = no timeout)
variablesobjectNoDeployment variables (key → value)
prompted_valuesobjectNoValues for prompted variables (variable id/name → value)
tenant_idstringNoTenant ID or SqId for tenant-aware deployments
environment_idstringYes*Environment ID or SqId (*required when tenant_id is provided)
bypass_cachebooleanNoSkip the file cache and fetch fresh content from S3/Git
dry_runbooleanNoValidate the deployment without executing it
override_freezebooleanNoBypass active deployment freezes (requires admin role)
override_reasonstringNoReason for overriding a freeze (required when override_freeze is true)

* Exactly one of solution_id, action_id, or sequence_id must be provided.

{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_mode": "sequential",
"failure_strategy": "stop_on_first_failure",
"rollback_strategy": "none",
"timeout_seconds": 3600,
"max_retries": 2
}

For rolling waves, pass target_mode as an object:

{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_mode": { "rolling": { "batch_size": 5 } }
}
{
"deployment_id": "d9e8f7c6-b5a4-3210-9876-543210fedcba",
"pending": false,
"message": "Deployment d9e8f7c6-b5a4-3210-9876-543210fedcba created and started successfully",
"deployment": {
"id": "d9e8f7c6-b5a4-3210-9876-543210fedcba",
"source_type": "solution",
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"solution_name": "API Service",
"solution_version": "1.4.2",
"sequence_id": null,
"sequence_name": null,
"sequence_version": null,
"action_id": null,
"action_name": null,
"action_version": null,
"state": "running",
"progress": 0.0,
"total_steps": 3,
"completed_steps": 0,
"failed_steps": 0,
"config": {
"target_mode": "parallel",
"failure_strategy": "stop_on_first_failure",
"rollback_strategy": "none",
"priority": 0,
"timeout_seconds": 3600,
"max_retries": 2,
"target_ids": ["5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12"],
"target_tags": [],
"variables": {},
"tenant_id": null,
"environment_id": null
},
"plan": {
"target_count": 1,
"steps": [
{
"id": "9b1d2c3e-4a5f-6789-0abc-def123456789",
"target_id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_name": "web-01.prod",
"sequence_id": "7a8b9c0d-1e2f-3456-7890-abcdef123456",
"sequence_name": "Deploy API",
"sequence_order": 1,
"action_id": "1a2b3c4d-5e6f-7890-abcd-ef0123456789",
"action_name": "Stop Service",
"action_order": 1,
"state": "pending",
"started_at": null,
"completed_at": null,
"exit_code": null,
"error_message": null,
"retry_count": 0,
"duration_ms": null
}
],
"resolved_variables": {},
"tenant_name": null,
"environment_name": null,
"created_at": "2024-12-01T12:00:00Z"
},
"tenant_id": null,
"tenant_name": null,
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "jane.operator",
"error_message": null,
"created_at": "2024-12-01T12:00:00Z",
"started_at": "2024-12-01T12:00:00Z",
"completed_at": null,
"duration_ms": null,
"analysis_enabled": false
}
}

HTTP Status: 202 Accepted

When no Thorax instance is available, the deployment is queued:

{
"pending_request_id": "3c4d5e6f-7a8b-9012-cdef-345678901234",
"pending": true,
"message": "Deployment request queued - waiting for eligible Thorax instance"
}

The deployment will be automatically dispatched when a Thorax instance becomes available.

Preview the execution plan without creating a deployment (dry-run).

POST /api/v1/deployments/preview
Authorization: Bearer <token>
Content-Type: application/json
{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Request body: Same as Create Deployment

{
"plan": {
"target_count": 3,
"steps": [
{
"id": "9b1d2c3e-4a5f-6789-0abc-def123456789",
"target_id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_name": "web-01.prod",
"sequence_id": "7a8b9c0d-1e2f-3456-7890-abcdef123456",
"sequence_name": "Deploy API",
"sequence_order": 1,
"action_id": "1a2b3c4d-5e6f-7890-abcd-ef0123456789",
"action_name": "Stop Service",
"action_order": 1,
"state": "pending",
"started_at": null,
"completed_at": null,
"exit_code": null,
"error_message": null,
"retry_count": 0,
"duration_ms": null
}
],
"resolved_variables": {},
"tenant_name": null,
"environment_name": null,
"created_at": "2024-12-01T12:00:00Z"
},
"warnings": ["Target web-03.prod is currently offline"],
"target_summary": {
"total": 3,
"online": 2,
"offline": 1,
"stale": 0
},
"variables": {},
"source_type": "solution",
"source_name": "API Service",
"source_version": "1.4.2"
}

Use this endpoint to:

  • Validate deployment configuration before execution
  • Show users what will happen
  • Check for deployment freezes
  • Verify target availability

Retrieve detailed information about a specific deployment.

GET /api/v1/deployments/{id}
Authorization: Bearer <token>
{
"id": "d9e8f7c6-b5a4-3210-9876-543210fedcba",
"source_type": "solution",
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"solution_name": "Web Application",
"solution_version": "2.1.0",
"sequence_id": null,
"sequence_name": null,
"sequence_version": null,
"action_id": null,
"action_name": null,
"action_version": null,
"state": "running",
"progress": 0.56,
"total_steps": 9,
"completed_steps": 5,
"failed_steps": 0,
"config": {
"target_mode": "parallel",
"failure_strategy": "stop_on_first_failure",
"rollback_strategy": "none",
"priority": 0,
"timeout_seconds": 3600,
"max_retries": 0,
"target_ids": [],
"target_tags": [],
"variables": {},
"tenant_id": null,
"environment_id": null
},
"plan": {
"target_count": 3,
"steps": [
{
"id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_id": "t1-uuid",
"target_name": "web-01.prod",
"sequence_id": "seq-uuid",
"sequence_name": "Deploy Web App",
"sequence_order": 0,
"action_id": "act-uuid",
"action_name": "Restart service",
"action_order": 0,
"state": "completed",
"started_at": "2024-01-20T14:30:05Z",
"completed_at": "2024-01-20T14:30:20Z",
"exit_code": 0,
"error_message": null,
"retry_count": 0,
"duration_ms": 15000
}
],
"resolved_variables": {},
"tenant_name": "Production",
"environment_name": "prod",
"created_at": "2024-01-20T14:30:00Z"
},
"tenant_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"tenant_name": "Production",
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "550e8400-e29b-41d4-a716-446655440000",
"error_message": null,
"created_at": "2024-01-20T14:30:00Z",
"started_at": "2024-01-20T14:30:00Z",
"completed_at": null,
"duration_ms": 45000,
"analysis_enabled": false
}

Cancel a running deployment.

POST /api/v1/deployments/{id}/cancel
Authorization: Bearer <token>
Content-Type: application/json
{
"reason": "Emergency rollback required"
}
FieldTypeRequiredDescription
reasonstringNoReason for cancellation (for audit log)

Returns the full deployment detail (the same DeploymentDetailResponse shape as Get Deployment Details) with state updated to cancelled:

{
"id": "d9e8f7c6-b5a4-3210-9876-543210fedcba",
"source_type": "solution",
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"solution_name": "Web Application",
"solution_version": "2.1.0",
"sequence_id": null,
"sequence_name": null,
"sequence_version": null,
"action_id": null,
"action_name": null,
"action_version": null,
"state": "cancelled",
"progress": 0.56,
"total_steps": 9,
"completed_steps": 5,
"failed_steps": 0,
"config": {
"target_mode": "parallel",
"failure_strategy": "stop_on_first_failure",
"rollback_strategy": "none",
"priority": 0,
"timeout_seconds": 3600,
"max_retries": 0,
"target_ids": [],
"target_tags": [],
"variables": {},
"tenant_id": null,
"environment_id": null
},
"plan": null,
"tenant_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"tenant_name": "Production",
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "jane.operator",
"error_message": null,
"created_at": "2024-01-20T14:30:00Z",
"started_at": "2024-01-20T14:30:00Z",
"completed_at": "2024-01-20T14:33:10Z",
"duration_ms": 190000,
"analysis_enabled": false
}

Re-execute a deployment with the same configuration.

Redeploy re-runs an existing deployment. With an empty body it reuses the original deployment’s configuration; any field you supply overrides the original.

POST /api/v1/deployments/{id}/redeploy
Authorization: Bearer <token>
Content-Type: application/json
{
"target_ids": ["t1-uuid", "t2-uuid"]
}

All fields are optional overrides applied on top of the original deployment.

FieldTypeDescription
variablesobjectOverride variables (merged with the original deployment’s variables)
environment_idstringOverride environment ID or SqId
target_idsarray of stringOverride target IDs or SqIds
target_tagsarray of objectOverride target tag filter — array of { key, value } pairs
target_modestring|objectOverride execution mode (sequential, parallel, or rolling object)
failure_strategystringOverride failure strategy
rollback_strategystringOverride rollback strategy
priorityintegerOverride priority (-100 to 100)
timeout_secondsintegerOverride per-command timeout (1–86400)
max_retriesintegerOverride max retries per command (0–10)
prompted_valuesobjectOverride prompted variable values
bypass_cachebooleanSkip the file cache and fetch fresh content
override_freezebooleanBypass active freezes (requires admin role)
override_reasonstringReason for overriding a freeze (required when override_freeze is true)

HTTP Status: 201 Created

Returns the newly created deployment as a DeploymentDetailResponse (the same shape as Get Deployment Details). Unlike Create Deployment, redeploy does not wrap the result in a { deployment_id, pending, message, deployment } envelope — the deployment detail is the entire response body. Because the new deployment was created from an existing one, it includes a source_deployment_id referencing the original:

{
"id": "a7b8c9d0-1e2f-3456-7890-abcdef123456",
"source_type": "solution",
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"solution_name": "Web Application",
"solution_version": "2.1.0",
"sequence_id": null,
"sequence_name": null,
"sequence_version": null,
"action_id": null,
"action_name": null,
"action_version": null,
"state": "running",
"progress": 0.0,
"total_steps": 2,
"completed_steps": 0,
"failed_steps": 0,
"config": {
"target_mode": "parallel",
"failure_strategy": "stop_on_first_failure",
"rollback_strategy": "none",
"priority": 0,
"timeout_seconds": 3600,
"max_retries": 0,
"target_ids": [
"5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"6a4d1b3f-2c8e-5f9b-0d22-3b5c7e9f1a34"
],
"target_tags": [],
"variables": {},
"tenant_id": null,
"environment_id": null
},
"plan": null,
"tenant_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"tenant_name": "Production",
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "jane.operator",
"error_message": null,
"created_at": "2024-01-20T15:00:00Z",
"started_at": "2024-01-20T15:00:00Z",
"completed_at": null,
"duration_ms": null,
"analysis_enabled": false,
"source_deployment_id": "d9e8f7c6-b5a4-3210-9876-543210fedcba"
}

Stream real-time deployment logs via Server-Sent Events (SSE).

GET /api/v1/deployments/{id}/logs?sse_token=<sse-token>

Each message is delivered as a single SSE data: line whose payload is a JSON object with a top-level event discriminator and a nested data object. The inner data object carries a type field matching event. No named SSE event: field is set, so browser EventSource clients receive these as default message events and switch on the parsed event/type value.

data: {"event":"ping","data":{"type":"ping"}}
data: {"event":"log","data":{"type":"log","log":{"timestamp":"2024-01-20T14:30:15+00:00","level":"info","step_id":null,"target_id":"5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12","target_name":"web-01.prod","message":"Starting deployment","metadata":null}}}
data: {"event":"progress","data":{"type":"progress","deployment_id":"d9e8f7c6-b5a4-3210-9876-543210fedcba","progress":0.33,"completed_steps":1,"total_steps":3,"message":"1 of 3 steps complete"}}
data: {"event":"step_update","data":{"type":"step_update","step_id":"9b1d2c3e-4a5f-6789-0abc-def123456789","state":"completed","exit_code":0,"error_message":null,"output":"Service stopped successfully\n"}}
data: {"event":"state_change","data":{"type":"state_change","deployment_id":"d9e8f7c6-b5a4-3210-9876-543210fedcba","old_state":"","new_state":"success","message":"Deployment completed successfully"}}

The event (and matching inner data.type) field is one of:

EventInner data fieldsDescription
ping(none)Keep-alive sent when the connection opens
loglog (object: timestamp, level, step_id, target_id, target_name, message, metadata)Individual log entry from a target
progressdeployment_id, progress, completed_steps, total_steps, messageProgress update (progress is 0.01.0)
step_updatestep_id, state, exit_code, error_message, outputStep-level status change
state_changedeployment_id, old_state, new_state, messageDeployment state transition

The terminal transition (success, failure, cancellation) arrives as a state_change event with the final new_state; there is no separate complete event.

const eventSource = new EventSource(
`https://mantis.example.com/api/v1/deployments/${deploymentId}/logs?sse_token=${sseToken}`
);
// Events are unnamed SSE messages; parse the payload and switch on `event`.
eventSource.onmessage = (message) => {
const { event, data } = JSON.parse(message.data);
switch (event) {
case 'log':
console.log(`[${data.log.level}] ${data.log.message}`);
break;
case 'progress':
updateProgress(data.completed_steps, data.total_steps);
break;
case 'step_update':
console.log(`Step ${data.step_id}: ${data.state}`);
break;
case 'state_change':
console.log(`State -> ${data.new_state}`);
if (
['success', 'failed', 'cancelled', 'rolled_back'].includes(
data.new_state
)
) {
eventSource.close();
}
break;
case 'ping':
break; // keep-alive
}
};
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
};

Retrieve completed deployment logs. Results are an offset-paginated array ordered chronologically.

GET /api/v1/deployments/{id}/logs/history?offset=0&limit=100
Authorization: Bearer <token>
ParameterTypeRequiredDescription
offsetintegerNoNumber of entries to skip (default: 0)
limitintegerNoMax entries to return (default: 100)

The response is a bare JSON array of log entries (not a paginated envelope):

[
{
"id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80912",
"timestamp": "2024-01-20T14:30:15+00:00",
"level": "info",
"event_type": "log",
"step_id": null,
"target_id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_name": "web-01.prod",
"message": "Starting deployment",
"stdout": null,
"stderr": null,
"metadata": null,
"analysis": null
},
{
"id": "3c4d5e6f-7081-92a3-b4c5-d6e7f8091223",
"timestamp": "2024-01-20T14:30:16+00:00",
"level": "info",
"event_type": "log",
"step_id": "9b1d2c3e-4a5f-6789-0abc-def123456789",
"target_id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_name": "web-01.prod",
"message": "Executing action: Stop Service",
"stdout": "Service stopped successfully\n",
"stderr": null,
"metadata": null,
"analysis": null
}
]

Get per-command results for a deployment, including AI-generated failure analysis where available. Only failed commands carry an analysis object.

GET /api/v1/deployments/{id}/analysis
Authorization: Bearer <token>

The response is a bare JSON array of command results ordered by start time. Each entry corresponds to a single command executed on a target. Optional fields (target_name, action_name, exit_code, stdout_tail, stderr_tail, error_message, analysis) are omitted when not present. stdout_tail and stderr_tail contain only the last 2000 characters of output.

[
{
"id": "4d5e6f70-8192-a3b4-c5d6-e7f809122334",
"target_id": "5f3c0a2e-1b7d-4e8a-9c11-2a4b6d8e0f12",
"target_name": "web-01.prod",
"action_name": "Stop Service",
"status": "completed",
"exit_code": 0,
"stdout_tail": "Service stopped successfully\n",
"started_at": "2024-01-20T14:30:05+00:00",
"completed_at": "2024-01-20T14:30:17+00:00",
"duration_ms": 12000
},
{
"id": "5e6f7081-92a3-b4c5-d6e7-f80912233445",
"target_id": "6a4d1b3f-2c8e-5f9b-0d22-3b5c7e9f1a34",
"target_name": "web-03.prod",
"action_name": "Restart Service",
"status": "failed",
"exit_code": 1,
"stderr_tail": "systemctl: Failed to restart api.service: Unit not found.\n",
"error_message": "Command exited with code 1",
"started_at": "2024-01-20T14:31:02+00:00",
"completed_at": "2024-01-20T14:31:09+00:00",
"duration_ms": 7000,
"analysis": {
"confidence": 0.92,
"summary": "The service restart failed because the api.service unit is not registered with systemd.",
"likely_cause": "The api.service unit file was never installed on this target, or was removed before the restart action ran.",
"remediation": [
"Verify the api.service unit file exists at /etc/systemd/system/api.service",
"Run `systemctl daemon-reload` after installing the unit file",
"Re-run the deployment once the unit is registered"
],
"analyzed_at": "2024-01-20T14:31:15+00:00",
"model_version": "claude-opus-4"
}
}
]

List deployments waiting in the dispatch queue.

GET /api/v1/deployments/pending?tenant_id=<uuid>&status=pending
Authorization: Bearer <token>
ParameterTypeRequiredDescription
tenant_idstring (UUID)NoFilter by tenant
statusstringNoFilter by status: pending, processing, dispatched, cancelled, failed

The response is a bare JSON array of pending dispatch requests. Optional fields (tenant_id, tenant_name, environment_id, environment_name, triggered_by, last_retry_at, error_message, deployment_id, dispatched_at, dispatched_to_instance_id) are omitted when not set — a freshly queued request typically has none of the dispatch-related fields:

[
{
"id": "3c4d5e6f-7a8b-9012-cdef-345678901234",
"tenant_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"tenant_name": "Production",
"environment_id": "e7d6c5b4-a3f2-1098-7654-3210fedcba98",
"environment_name": "prod",
"triggered_by": "jane.operator",
"queued_at": "2024-01-20 14:35:00",
"retry_count": 0,
"status": "pending",
"created_at": "2024-01-20 14:35:00"
}
]

Remove a deployment from the dispatch queue.

DELETE /api/v1/deployments/pending/{id}
Authorization: Bearer <token>

HTTP Status: 200 OK

Returns an empty body on success. A 404 is returned if the pending request does not exist, and a 409 Conflict if it is already in a terminal state (dispatched, cancelled, or failed) — only pending or processing requests can be cancelled.

CI/CD Pipeline Integration (GitHub Actions)

Section titled “CI/CD Pipeline Integration (GitHub Actions)”
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via Mantis API
env:
MANTIS_URL: ${{ secrets.MANTIS_URL }}
MANTIS_API_KEY: ${{ secrets.MANTIS_API_KEY }}
SOLUTION_ID: ${{ secrets.SOLUTION_ID }}
run: |
# Create deployment
RESPONSE=$(curl -s -X POST "$MANTIS_URL/api/v1/deployments" \
-H "Authorization: Bearer $MANTIS_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"solution_id\": \"$SOLUTION_ID\",
\"target_tags\": [
{\"key\": \"env\", \"value\": \"production\"},
{\"key\": \"role\", \"value\": \"webserver\"}
],
\"target_tags_match_all\": true
}")
DEPLOYMENT_ID=$(echo "$RESPONSE" | jq -r '.deployment_id')
echo "Deployment ID: $DEPLOYMENT_ID"
# Poll until complete (the detail endpoint exposes `state`)
while true; do
STATE=$(curl -s "$MANTIS_URL/api/v1/deployments/$DEPLOYMENT_ID" \
-H "Authorization: Bearer $MANTIS_API_KEY" \
| jq -r '.state')
echo "State: $STATE"
case "$STATE" in
success|rolled_back)
echo "Deployment finished: $STATE"
exit 0
;;
failed|cancelled)
echo "Deployment $STATE"
exit 1
;;
*)
sleep 10
;;
esac
done
interface DeploymentClient {
create(options: CreateDeploymentOptions): Promise<Deployment>;
get(id: string): Promise<DeploymentDetail>;
cancel(id: string, reason?: string): Promise<void>;
streamLogs(id: string, onLog: (log: LogEntry) => void): () => void;
}
class MantisDeploymentClient implements DeploymentClient {
constructor(
private baseUrl: string,
private apiKey: string
) {}
async create(options: CreateDeploymentOptions): Promise<Deployment> {
const response = await fetch(`${this.baseUrl}/api/v1/deployments`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(options),
});
if (!response.ok) {
const error = await response.json();
throw new DeploymentError(error.error.code, error.error.message);
}
return response.json();
}
streamLogs(id: string, onLog: (log: LogEntry) => void): () => void {
const eventSource = new EventSource(
`${this.baseUrl}/api/v1/deployments/${id}/logs?sse_token=${this.sseToken}`
);
// Events are unnamed SSE messages: { event, data }. Forward log entries.
eventSource.onmessage = (message) => {
const { event, data } = JSON.parse(message.data);
if (event === 'log') {
onLog(data.log);
}
};
// Return cleanup function
return () => eventSource.close();
}
}