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&per_page=50&tenant_id=<uuid>
Authorization: Bearer <token>
ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
per_pageintegerNoItems 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,
"per_page": 50,
"total_pages": 5,
"has_next": true,
"has_prev": false
},
"status_counts": {
"all": 247,
"running": 3,
"failed": 12,
"succeeded": 230,
"other": 2
}
}

status_counts is a top-level sibling of data/meta. GET /api/v1/targets carries one too, with different buckets (all/online/stale/offline/unknown). It reports totals per status bucket across all pages and ignores the status filter (so the chips stay stable while you filter): all is the count matching the non-status filters, plus running, failed, succeeded and other.

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
pendingCreated, awaiting dispatch to a Thorax instance
queuedDispatched to a Thorax instance, not yet started
runningCurrently executing
successCompleted successfully
failedCompleted with failures
cancelledUser-cancelled deployment
rolled_backSuperseded by a rollback that succeeded

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 a destination: target_ids (explicit UUIDs), target_tags (key-value pairs), or one of the off-target fields execution_thorax_instance_id / execution_pool_id. Supplying none returns a 400, and so does combining an off-target field with target_ids/target_tags — off-target execution replaces target selection rather than narrowing it.

Deploy to an explicit list of targets (UUIDs):

{
"solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_ids": [
"019b937d-31af-8c8a-a5fa-adc06d646ec6",
"019b937d-e03c-8407-a36e-92742139b550",
"019b937d-fccb-8794-af79-58d9d958fb6d"
]
}
FieldTypeRequiredDescription
solution_idstringYes*Solution to deploy (UUID)
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
target_tagsarray of objectNoTag filter — array of { key, value } pairs
target_tags_match_allbooleanNoRequire ALL target_tags to match (default: any)
execution_thorax_instance_idstringNoRun every step off-target on this Thorax instance instead of on targets. Requires deployments:execute_off_target
execution_pool_idstringNoRun every step off-target on an eligible member of this execution pool; mutually exclusive with execution_thorax_instance_id. Requires deployments:execute_off_target
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–86400). There is no “no timeout” — see below
variablesobjectNoDeployment variables (key → value)
prompted_valuesobjectNoValues for prompted variables (variable id/name → value)
tenant_idstringNoTenant ID for tenant-aware deployments
environment_idstringYes*Environment ID (*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 deployments:override_freeze)
override_reasonstringNoReason for overriding a freeze (required when override_freeze is true)
on_failure_action_idstringNoAction to run when this deployment fails; mutually exclusive with the other two on_failure_* fields
on_failure_sequence_idstringNoSequence to run when this deployment fails
on_failure_solution_idstringNoSolution to run when this deployment fails

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

deployment_timeout_seconds is always bounded

Section titled “deployment_timeout_seconds is always bounded”

Omitting the field, or sending 0, does not mean “run forever” — it means “derive it from the plan” (steps × timeout_seconds × (1 + max_retries) plus a buffer). Whichever number results, requested or derived, it is then clamped to the platform range of one hour to one day: a request below an hour is raised to an hour, and no deployment runs past 24 hours before being failed as timed out.

{
"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

The deployment is queued when another deployment is running on one of its targets — two deployments never run on the same target at once — or when no Thorax instance can take it yet. The message says which:

{
"pending_request_id": "3c4d5e6f-7a8b-9012-cdef-345678901234",
"pending": true,
"message": "Deployment request queued: waiting for target web-01 while deployment 9f8e7d6c-5b4a-3210-fedc-ba9876543210 runs on it"
}

The deployment is dispatched automatically once it can be. One waiting for a busy target fails if the target is still held once the deployment has been queued for MANDIBLE__DISPATCH__BUSY_TARGET_MAX_WAIT_SECS (24 hours by default).

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
  • Verify target availability

Preview does not check deployment freezes — its warnings field only reports offline and stale targets. Call GET /api/v1/deployment-freezes/check with the relevant solution_id/environment_id to test whether a deployment would be blocked; creating one during a freeze returns 409 DEPLOYMENT_FROZEN.

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": "019b937d-31af-8c8a-a5fa-adc06d646ec6",
"target_name": "web-01.prod",
"sequence_id": "019b937d-750a-8cfd-a358-f2c4751f3151",
"sequence_name": "Deploy Web App",
"sequence_order": 0,
"action_id": "019b937d-bd73-823d-a173-d845b9f9c382",
"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": "user: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": ["019b937d-31af-8c8a-a5fa-adc06d646ec6", "019b937d-e03c-8407-a36e-92742139b550"]
}

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
target_idsarray of stringOverride target IDs
target_tagsarray of objectOverride target tag filter — array of { key, value } pairs
target_tags_match_allbooleanOverride tag matching: require ALL target_tags to match (omitted: as the original)
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)
deployment_timeout_secondsintegerOverride the overall deployment timeout in seconds (0–86400)
prompted_valuesobjectOverride prompted variable values
bypass_cachebooleanSkip the file cache and fetch fresh content
override_freezebooleanBypass active freezes (requires deployments:override_freeze)
override_reasonstringReason for overriding a freeze (required when override_freeze is true)
on_failure_action_idstringOverride the failure handler with an action
on_failure_sequence_idstringOverride the failure handler with a sequence
on_failure_solution_idstringOverride the failure handler with a solution
execution_thorax_instance_idstringRun off-target on this Thorax instance instead of the one the original ran on
execution_pool_idstringRun off-target on a member of this execution pool instead of the original’s instance

HTTP Status: 201 Created, or 202 Accepted when another deployment is running on one of its targets and the new one is queued behind it.

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.0–1.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, ordered chronologically.

GET /api/v1/deployments/{id}/logs/history?page=1&per_page=50
Authorization: Bearer <token>
ParameterTypeRequiredDescription
pageintegerNoPage number, 1-indexed (default: 1)
per_pageintegerNoItems per page (default: 50, max: 100)

The standard paginated envelope:

{
"data": [
{
"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
}
],
"meta": {
"total": 247,
"page": 1,
"per_page": 50,
"total_pages": 5,
"has_next": true,
"has_prev": false
}
}

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?page=1&per_page=50
Authorization: Bearer <token>
ParameterTypeRequiredDescription
pageintegerNoPage number, 1-indexed (default: 1)
per_pageintegerNoItems per page (default: 50, max: 100)

The standard paginated envelope, 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.

{
"data": [
{
"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"
}
}
],
"meta": {
"total": 2,
"page": 1,
"per_page": 50,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}

List deployments waiting in the dispatch queue.

GET /api/v1/deployments/pending?tenant_id=<uuid>&status=pending&page=1&per_page=50
Authorization: Bearer <token>
ParameterTypeRequiredDescription
tenant_idstring (UUID)NoFilter by tenant
statusstringNoFilter by status: pending, processing, dispatched, cancelled, failed
pageintegerNoPage number, 1-indexed (default: 1)
per_pageintegerNoItems per page (default: 50, max: 100)

The standard paginated envelope, newest first. 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:

{
"data": [
{
"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"
}
],
"meta": {
"total": 1,
"page": 1,
"per_page": 50,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}

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. Only a pending request can be cancelled: the cancel is an atomic conditional update guarded on status = pending, so a request the dispatch queue has already claimed (processing) — or one in a terminal state (dispatched, cancelled, failed) — returns 409 Conflict rather than masking an in-flight dispatch as a success.

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
}")
# A queued request (202) carries a pending_request_id and no deployment yet
if [ "$(echo "$RESPONSE" | jq -r '.pending')" = "true" ]; then
echo "Queued, not started: $(echo "$RESPONSE" | jq -r '.message')"
exit 1
fi
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,
sseToken: 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();
}
// `sseToken` is single-use: mint one with POST /api/v1/auth/sse-token per stream.
streamLogs(
id: string,
sseToken: string,
onLog: (log: LogEntry) => void
): () => void {
const eventSource = new EventSource(
`${this.baseUrl}/api/v1/deployments/${id}/logs?sse_token=${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();
}
}