API Reference

Stategraph provides a REST API for programmatic access to states, queries, and management operations.

Authentication

Stategraph supports two authentication methods:

Bearer Token (API Key)

For CLI, Terraform, and programmatic access, use Bearer authentication with API keys:

curl -H "Authorization: Bearer $STATEGRAPH_API_KEY" http://localhost:8080/api/v1/...

API keys are created via the Settings page in the UI. For Terraform HTTP backend, use HTTP Basic Auth with username session and the API key as password.

For browser-based access, authentication uses session cookies set after login (local authentication or OAuth). This is handled automatically by the browser.

Authentication Modes

Stategraph supports three authentication modes:
- Local Authentication - Email/password with user management (default)
- Google OAuth - SSO via Google Workspace
- Generic OIDC - Any OIDC-compatible provider

Base URL

http://localhost:8080/api/v1

Replace localhost:8080 with your Stategraph server address if different.


User Endpoints

Get Current User

GET /api/v1/whoami

Returns information about the authenticated user.

Response

{
  "id": "f30ed1f9-be44-46a3-9050-03e9561e94f0",
  "name": "User Name",
  "email": "user@example.com",
  "is_admin": false,
  "type": "user"
}
Field Type Description
id string Unique user ID (UUID)
name string Display name
email string User email address
is_admin boolean Whether user has admin privileges
type enum user, api, or system

List User Tenants

GET /api/v1/user/tenants

Returns tenants the user has access to.

Response

{
  "results": [
    {
      "id": "tenant-123",
      "name": "My Organization"
    }
  ]
}

Authentication Endpoints

Get Login Options

GET /api/v1/login/options

Returns available authentication methods.

Response

When local authentication is the only method configured:

{
  "options": []
}

When OAuth is configured:

{
  "options": [
    {
      "type": "google",
      "display_name": "Sign in with Google"
    }
  ]
}

Login with Password

POST /api/v1/login/password

Authenticates user with email and password (local authentication only).

Request Body

{
  "email": "user@example.com",
  "password": "your-password"
}

Response

{
  "session_token": "eyJhbGciOiJIUzI1NiIs...",
  "success": true
}

The session_token can be used for Bearer authentication. A session cookie is also set for browser-based access.

Check Setup Status

GET /api/v1/setup/status

Returns whether initial setup is needed (no users exist).

Response

{
  "needs_setup": false
}

Create First Admin User

POST /api/v1/setup/admin

Creates the first admin user during initial setup. Only works when no users exist.

Request Body

{
  "email": "admin@example.com",
  "password": "secure-password",
  "name": "Admin User",
  "organization": "My Organization"
}

Response

{
  "session_token": "eyJhbGciOiJIUzI1NiIs...",
  "user_id": "f30ed1f9-be44-46a3-9050-03e9561e94f0"
}

The session_token can be used for Bearer authentication.

Complete Setup

POST /api/v1/setup/complete

Marks the initial setup as complete. Called after creating the first admin user.

Response: 200 OK

Logout

GET /api/v1/logout

Clears session cookie and logs out the user.

Response: 200 OK with redirect


User Management Endpoints

All user management endpoints require admin authentication.

List Users

GET /api/v1/users

Returns list of users with cursor-based pagination and filtering.

Parameters

Parameter Type Required Description
type query No Filter by user type: user or api
limit query No Max results (default: 25)
cursor query No Pagination cursor from previous response
search query No Search by name or email

Response

{
  "users": [
    {
      "id": "f30ed1f9-be44-46a3-9050-03e9561e94f0",
      "name": "John Doe",
      "email": "john@example.com",
      "is_admin": false,
      "type": "user",
      "auth_origin": "local",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total_count": 10,
  "limit": 25,
  "has_more": false,
  "next_cursor": null
}
Field Type Description
users array List of user objects
total_count integer Total number of matching users
limit integer Limit used for this request
has_more boolean Whether more results are available
next_cursor string Cursor for next page (null if no more)
auth_origin string Authentication origin (e.g., local, google, oidc)

Get User Details

GET /api/v1/users/detail?user_id={user_id}

Returns detailed information about a specific user.

Response

{
  "id": "f30ed1f9-be44-46a3-9050-03e9561e94f0",
  "name": "John Doe",
  "email": "john@example.com",
  "is_admin": false,
  "type": "user",
  "created_at": "2024-01-15T10:30:00Z",
  "last_login": "2024-01-20T14:30:00Z"
}

Create User

POST /api/v1/users

Creates a new user account.

Request Body

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "password": "secure-password",
  "is_admin": false
}
Field Type Required Description
name string Yes Display name
email string Yes Email address (used for login)
password string Yes Password (8-128 characters)
is_admin boolean No Grant admin privileges (default: false)

Response

{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "is_admin": false,
  "type": "user"
}

Update User

PUT /api/v1/users/update?user_id={user_id}

Updates user information.

Request Body

{
  "name": "Jane Smith",
  "email": "jane.smith@example.com"
}

Response: 200 OK with updated user object

Delete User

DELETE /api/v1/users/delete?user_id={user_id}

Soft-deletes a user (marks as deleted, invalidates API keys).

Response: 204 No Content

Change User Password

POST /api/v1/users/change-password?user_id={user_id}

Changes a user's password.

Request Body

{
  "new_password": "new-secure-password",
  "current_password": "old-password"
}
Field Type Required Description
new_password string Yes New password (8-128 characters)
current_password string Conditional Required when changing own password

Response: 200 OK

Note: Admins can change any user's password without providing current_password. Users changing their own password must provide current_password.

Toggle Admin Status

POST /api/v1/users/toggle-admin?user_id={user_id}

Grants or revokes admin privileges.

Request Body

{
  "is_admin": true
}

Response: 200 OK with updated user object

Note: Cannot remove admin privileges from the last admin user.

Create Service Account

POST /api/v1/api-users

Creates a service account (API user) for programmatic access.

Request Body

{
  "name": "ci-production",
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}

Response

{
  "user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Important: The token is only returned once. Save it immediately.


State Endpoints

List States

GET /api/v1/tenants/{tenant_id}/states

Returns all active (non-deleted) states for a tenant. Soft-deleted states are excluded from results.

Parameters

Parameter Type Required Description
tenant_id path Yes Tenant ID
page query No Pagination cursor
limit query No Max results (default: 100)
q query No MQL filter query

Response

{
  "results": [
    {
      "id": "state-123",
      "name": "networking",
      "group_id": "networking",
      "workspace": "production",
      "created_at": "2024-01-15T10:30:00Z",
      "deleted_at": null,
      "deleted_by": null
    }
  ]
}

Create State

POST /api/v1/tenants/{tenant_id}/states

Creates a new state.

Request Body

{
  "name": "networking",
  "group_id": "networking",
  "workspace": "production"
}
Field Type Required Description
name string Yes State name
group_id string No Group identifier (defaults to name)
workspace string No Workspace name (default: default)

Response: 201 Created with state object

Import State

POST /api/v1/tenants/{tenant_id}/states/import

Imports an existing Terraform state file.

Request Body

{
  "name": "networking",
  "group_id": "networking",
  "workspace": "production",
  "state": { ... },
  "tags": { "source": "migration" }
}
Field Type Required Description
name string Yes State name
state object Yes Terraform state JSON
group_id string No Group identifier
workspace string No Workspace name
tags object No Metadata tags

Delete State

DELETE /api/v1/states/{state_id}

Deletes a state. Supports both soft delete (default) and hard delete modes.

Parameters

Parameter Type Required Description
state_id path Yes State ID (UUID)
hard_delete query No If true, performs hard delete (default: false)

Deletion Modes

Soft Delete (default)
- Sets deleted_at timestamp and deleted_by user ID
- State remains in database but is marked as deleted
- Preserves historical data for audit purposes

Hard Delete (hard_delete=true)
- Permanently removes state and all related data:
- Resources
- Instances
- Providers
- Outputs
- Raw states
- Transaction logs
- Check entries and results
- Cannot be undone

Authorization

Requires admin privileges.

Response Codes

Code Description
200 OK State successfully deleted
401 Unauthorized Not authenticated
403 Forbidden User is not an admin
404 Not Found State does not exist
500 Internal Server Error Server error occurred

Examples

Soft delete:

curl -X DELETE "http://localhost:8080/api/v1/states/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"

Hard delete:

curl -X DELETE "http://localhost:8080/api/v1/states/550e8400-e29b-41d4-a716-446655440000?hard_delete=true" \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"

Error Response (403 Forbidden)

{
  "id": "ADMIN_REQUIRED",
  "data": "Admin privileges required"
}

Error Response (404 Not Found)

{
  "id": "STATE_NOT_FOUND",
  "data": "State not found"
}

Backend Endpoints

These endpoints implement the Terraform HTTP backend protocol.

Get State

GET /api/v1/states/backend/{group_id}
GET /api/v1/states/backend/{group_id}/{workspace}

Retrieves the current state for Terraform.

Response: Raw Terraform state JSON

Set State

POST /api/v1/states/backend/{group_id}
POST /api/v1/states/backend/{group_id}/{workspace}

Updates state from Terraform.

Request Body: Raw Terraform state JSON

Response: 200 OK


Instance Endpoints

List Instances

GET /api/v1/states/{state_id}/instances

Returns resource instances for a state.

Parameters

Parameter Type Required Description
state_id path Yes State ID
page query No Pagination cursor
limit query No Max results
q query No MQL filter

Response

{
  "results": [
    {
      "address": "aws_instance.web",
      "type": "aws_instance",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "module": null,
      "attributes": { ... },
      "dependencies": ["aws_subnet.main", "aws_security_group.web"]
    }
  ]
}

Get Blast Radius

GET /api/v1/states/{state_id}/instances/{instance_address}/blast-radius

Returns resources affected by changes to the specified instance.

Parameters

Parameter Type Required Description
state_id path Yes State ID
instance_address path Yes URL-encoded instance address

Response

{
  "results": [
    {
      "address": "aws_eip.web",
      "resource_address": "aws_eip.web",
      "distance": 1
    }
  ]
}

Module Endpoints

List Modules

GET /api/v1/states/{state_id}/modules

Returns modules in a state.

Response

{
  "results": [
    {
      "name": "module.vpc",
      "instance_count": 25,
      "resource_count": 10
    }
  ]
}

Summary Endpoints

State Summary

GET /api/v1/states/{state_id}/summary

Returns aggregate statistics for a state.

Response

{
  "instances": 150,
  "resources": 45,
  "modules": 8,
  "providers": 3,
  "edges": 234
}

Resources Summary

GET /api/v1/states/{state_id}/resources/summary

Returns instance counts by resource type.

Response

{
  "aws_instance": { "instances": 20 },
  "aws_security_group": { "instances": 15 },
  "aws_subnet": { "instances": 6 }
}

Query Endpoints

Execute MQL Query

GET /api/v1/mql

Executes an MQL query across all states.

Parameters

Parameter Type Required Description
q query Yes MQL query string
page query No Pagination cursor
tz query No Timezone for date formatting

Example

curl "http://localhost:8080/api/v1/mql?q=SELECT%20*%20FROM%20instances%20WHERE%20type%20%3D%20%27aws_instance%27" \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"

Response

[
  {
    "address": "aws_instance.web",
    "type": "aws_instance",
    ...
  }
]

Get MQL Schema

GET /api/v1/mql/schema

Returns the MQL schema for autocomplete.


Transaction Endpoints

List Transactions

GET /api/v1/tenants/{tenant_id}/tx

Returns transactions for a tenant.

Response

{
  "results": [
    {
      "id": "455fe705-f27f-4335-9355-dbe8f14098df",
      "created_at": "2024-01-15T10:30:00Z",
      "created_by": "f30ed1f9-be44-46a3-9050-03e9561e94f0",
      "completed_at": "2024-01-15T10:30:05Z",
      "completed_by": "f30ed1f9-be44-46a3-9050-03e9561e94f0",
      "state": "completed",
      "tags": {"desc": "Backend update"}
    }
  ]
}

Create Transaction

POST /api/v1/tenants/{tenant_id}/tx/create

Creates a new transaction.

Request Body

{
  "tags": {
    "pipeline": "github-actions",
    "commit": "abc123"
  }
}

Get Transaction Logs

GET /api/v1/tx/{tx_id}/logs

Returns logs for a transaction.

Response

{
  "results": [
    {
      "id": "log-123",
      "action": "state_set",
      "object_type": "instance",
      "created_at": "2024-01-15T10:30:00Z",
      "state_id": "state-123",
      "data": { ... }
    }
  ]
}

Abort Transaction

POST /api/v1/tx/{tx_id}/abort

Aborts an active transaction.


API Key Endpoints

Create API Key

POST /api/v1/tx/session/create

Creates an API key for CLI, Terraform, and programmatic access. API keys do not expire.

Request Body

{
  "tags": {
    "name": "my-api-key"
  }
}

Response

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Gap Analysis Endpoints

Get Gap Analysis Config

GET /api/v1/tenants/{tenant_id}/gaps/config

Returns gap analysis configuration status.

Parameters

Parameter Type Required Description
provider query Yes Cloud provider (e.g., aws)

Response

{
  "status": "ready",
  "ready_for_gap_analysis": true,
  "has_aggregator": true,
  "aggregator_region": "us-east-1",
  "indexed_regions": ["us-east-1", "us-west-2"],
  "index_count": 1500,
  "warnings": []
}

Run Gap Analysis

GET /api/v1/tenants/{tenant_id}/gaps

Returns unmanaged resources.

Parameters

Parameter Type Required Description
provider query Yes Cloud provider
source query No cache (default) or no-cache

Response

{
  "summary": {
    "total_aws_resources": 1500,
    "managed_by_stategraph": 1200,
    "unmanaged": 300
  },
  "unmanaged_resources": [ ... ],
  "fetched_at": 1705312800,
  "from_cache": true
}

Generate Import

POST /api/v1/tenants/{tenant_id}/gaps/import

Generates Terraform import blocks.

Request Body

{
  "provider": "aws",
  "resources": [
    {
      "arn": "arn:aws:s3:::bucket-name",
      "service": "S3",
      "resource_type": "AWS::S3::Bucket",
      "region": "us-east-1",
      "owning_account_id": "123456789012"
    }
  ]
}

Response

{
  "import_blocks": "import { ... }",
  "provider_hcl": "provider \"aws\" { ... }",
  "generated_hcl": "resource \"aws_s3_bucket\" { ... }",
  "supported_count": 1,
  "unsupported_count": 0,
  "unsupported_resources": []
}

Error Responses

400 Bad Request

{
  "id": "bad-request",
  "data": "Error description"
}

401 Unauthorized

No body. Authentication required.

404 Not Found

Resource not found.

500 Internal Server Error

Server error occurred.


Pagination

List endpoints support cursor-based pagination:

# First page
curl "http://localhost:8080/api/v1/tenants/$TENANT_ID/states?limit=50" \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"

# Next page (use cursor from previous response)
curl "http://localhost:8080/api/v1/tenants/$TENANT_ID/states?limit=50&page=cursor..." \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"