IaC API¶
The IaC API provides endpoints for ingesting infrastructure-as-code run reports, stack definitions, module drift data, and resource topologies, as well as querying that data from the Farm dashboard.
For interactive documentation, including all available endpoints, data models, and request/response examples, please refer to the Swagger UI.
Endpoints¶
| Method | Path | Description | Auth |
|---|---|---|---|
POST | /api/v1/iac/runs/ingest | Ingest a plan or apply run report from Cultivator or CI | IAC_INGEST_TOKEN |
POST | /api/v1/iac/stacks/import | Bulk-import stack definitions from Cultivator discovery output | IAC_INGEST_TOKEN |
POST | /api/v1/iac/module-drift/ingest | Ingest module drift data from Agronomist | IAC_INGEST_TOKEN |
POST | /api/v1/iac/stacks/:id/resources/ingest | Atomically replace the resource topology for a stack | IAC_INGEST_TOKEN |
GET | /api/v1/iac/stacks | List all stacks with optional ?environment= and ?componentId= filters | JWT |
GET | /api/v1/iac/stacks/:id | Get a single stack by UUID | JWT |
GET | /api/v1/iac/stacks/:id/runs | List paginated run history for a stack | JWT |
GET | /api/v1/iac/stacks/:id/resources | Get the resource topology for a stack | JWT |
GET | /api/v1/iac/dashboard | Get the IaC dashboard summary grouped by environment | JWT |
GET | /api/v1/iac/module-drift | List all module drift records ordered newest first | JWT |
Authentication¶
The IaC API uses two distinct authentication mechanisms depending on the caller.
JWT Bearer (user-facing endpoints)¶
All GET endpoints require a JWT issued by the Farm auth service. Pass the token in the Authorization header:
IAC_INGEST_TOKEN (machine-to-machine ingest endpoints)¶
Ingest endpoints are called by automated tooling (Cultivator, Agronomist, CI pipelines) and do not use JwtAuthGuard. Instead they validate a static bearer token configured on the API server via the IAC_INGEST_TOKEN environment variable.
Generate a secure token for your deployment with:
Set the resulting value as IAC_INGEST_TOKEN in the API server environment. All ingest endpoints return 401 if the header is absent or the token does not match.
Ingesting a Run Report¶
Records a completed Terraform or OpenTofu plan or apply run. Called by Cultivator or a CI pipeline after each run completes.
curl -X POST http://localhost:3000/api/v1/iac/runs/ingest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <iac-ingest-token>" \
-d '{
"stackName": "core-networking",
"environment": "production",
"provider": "terraform",
"type": "apply",
"status": "succeeded",
"resourceChanges": { "add": 2, "change": 1, "destroy": 0 },
"triggeredBy": "github-actions",
"pipelineUrl": "https://github.com/acme/infra/actions/runs/123",
"startedAt": "2026-04-01T10:00:00Z",
"finishedAt": "2026-04-01T10:04:30Z",
"durationMs": 270000
}'
Response (201)¶
Returns the persisted IacRun object.
{
"id": "uuid",
"stackName": "core-networking",
"environment": "production",
"provider": "terraform",
"type": "apply",
"status": "succeeded",
"resourceChanges": { "add": 2, "change": 1, "destroy": 0 },
"triggeredBy": "github-actions",
"pipelineUrl": "https://github.com/acme/infra/actions/runs/123",
"startedAt": "2026-04-01T10:00:00Z",
"finishedAt": "2026-04-01T10:04:30Z",
"durationMs": 270000,
"createdAt": "2026-04-01T10:00:00Z"
}
Importing Stacks¶
Bulk-upserts IaC stack definitions from a Cultivator discovery run. Records are matched by name + environment; existing records are updated in place. The componentId and externalToolUrl fields are preserved on an update unless they are explicitly re-sent in the payload.
curl -X POST http://localhost:3000/api/v1/iac/stacks/import \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <iac-ingest-token>" \
-d '{
"stacks": [
{
"name": "core-networking",
"environment": "production",
"provider": "terraform",
"repositoryUrl": "https://github.com/acme/infra",
"basePath": "stacks/core-networking",
"externalToolUrl": "https://app.terraform.io/app/acme/workspaces/core-networking"
}
]
}'
Response (201)¶
Ingesting Module Drift¶
Records outdated module references detected by an Agronomist scan. Farm computes versionsBehind automatically from the semver distance between currentRef and latestRef. Non-semver refs (e.g., commit SHAs or branch names) default to versionsBehind: 1.
curl -X POST http://localhost:3000/api/v1/iac/module-drift/ingest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <iac-ingest-token>" \
-d '{
"modules": [
{
"stackPath": "stacks/networking/main.tf",
"moduleName": "terraform-aws-modules/vpc/aws",
"sourceUrl": "https://registry.terraform.io/terraform-aws-modules/vpc/aws",
"currentRef": "v3.14.0",
"latestRef": "v3.19.0"
}
]
}'
Returns 201 with an empty body.
Listing Stacks¶
Returns all IaC stacks. Each record includes the most recent run summary as latestRun. Supports optional query parameters to narrow results.
| Parameter | Type | Description |
|---|---|---|
environment | string | Filter by environment name (e.g., production) |
componentId | string (UUID) | Filter by linked Service Catalog component |
curl -H "Authorization: Bearer <token>" \
"http://localhost:3000/api/v1/iac/stacks?environment=production"
Response (200)¶
[
{
"id": "uuid",
"name": "core-networking",
"environment": "production",
"provider": "terraform",
"repositoryUrl": "https://github.com/acme/infra",
"basePath": "stacks/core-networking",
"externalToolUrl": "https://app.terraform.io/app/acme/workspaces/core-networking",
"componentId": "uuid or null",
"latestRun": {
"id": "uuid",
"type": "apply",
"status": "succeeded",
"resourceChanges": { "add": 0, "change": 1, "destroy": 0 },
"triggeredBy": "github-actions",
"startedAt": "2026-04-01T10:00:00Z",
"durationMs": 270000
},
"createdAt": "2026-04-01T00:00:00Z",
"updatedAt": "2026-04-01T10:05:00Z"
}
]
Getting a Stack¶
Returns a single IaC stack by its UUID, including the most recent run summary.
Returns 404 if no stack with the given ID exists.
Listing Runs for a Stack¶
Returns paginated run history for a specific stack, ordered newest first.
| Parameter | Type | Default | Maximum |
|---|---|---|---|
page | number | 1 | — |
limit | number | 20 | 100 |
curl -H "Authorization: Bearer <token>" \
"http://localhost:3000/api/v1/iac/stacks/{id}/runs?page=1&limit=20"
Response (200)¶
{
"data": [
{
"id": "uuid",
"type": "apply",
"status": "succeeded",
"resourceChanges": { "add": 2, "change": 1, "destroy": 0 },
"triggeredBy": "github-actions",
"pipelineUrl": "https://github.com/acme/infra/actions/runs/123",
"startedAt": "2026-04-01T10:00:00Z",
"finishedAt": "2026-04-01T10:04:30Z",
"durationMs": 270000,
"createdAt": "2026-04-01T10:00:00Z"
}
],
"total": 42
}
Returns 404 if no stack with the given ID exists.
IaC Dashboard¶
Returns a summary of all stacks grouped by environment, intended for the Farm dashboard overview page.
Returns 200 with a DashboardDto containing per-environment stack summaries.
Module Drift Records¶
Returns all module drift records ordered by detection time, newest first.
Returns 200 with an array of IacModuleDrift records.
Ingesting Resource Topology¶
Atomically replaces the full resource topology (nodes and edges) for a stack. The previous topology for the stack is deleted before the new one is inserted, so each call represents the complete current state.
curl -X POST http://localhost:3000/api/v1/iac/stacks/{id}/resources/ingest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <iac-ingest-token>" \
-d '{
"resources": [
{
"address": "aws_vpc.main",
"type": "aws_vpc",
"provider": "aws",
"name": "main"
}
],
"dependencies": [
{
"sourceAddress": "aws_subnet.public",
"targetAddress": "aws_vpc.main"
}
]
}'
Returns 201 with an empty body. Returns 404 if no stack with the given ID exists.
Getting Resource Topology¶
Returns the resource topology for a stack as a graph of nodes and directed edges.
Response (200)¶
{
"nodes": [
{
"address": "aws_vpc.main",
"type": "aws_vpc",
"provider": "aws",
"name": "main"
}
],
"edges": [
{
"source": "aws_subnet.public",
"target": "aws_vpc.main"
}
]
}
Returns 404 if no stack with the given ID exists.
Error Responses¶
| Status | Condition |
|---|---|
400 | Request body failed validation (any endpoint) |
401 | Authorization header is missing or the token does not match IAC_INGEST_TOKEN (ingest endpoints only) |
404 | The requested stack UUID does not exist |
500 | Unexpected server error |