Stategraph API

The Stategraph REST API gives programmatic access to users and tenants, states, queries, transactions, costs, security findings, VCS installations, and the key-value store. Use it to add Stategraph to your CI/CD pipelines, custom workflows, and automation. The stategraph CLI and the console use it too.

Beta

These endpoints are in beta and can change: the GitHub and GitLab user and installation endpoints, stacks, the key-value store, refresh tokens, the server configuration, and the GitHub client ID. Version your integrations and watch for updates.

Base URL

  • Stategraph Cloud: https://app.stategraph.cloud
  • A self-hosted server: its public base URL, the value of STATEGRAPH_UI_BASE
  • A local Docker Compose stack: http://localhost:8080

Each path starts with /api. For example, GET /api/v1/whoami on Stategraph Cloud is https://app.stategraph.cloud/api/v1/whoami. In a path, {vcs} is github for GitHub or gitlab for GitLab. Request bodies are JSON (Content-Type: application/json).

Some examples use $STATEGRAPH_API_BASE for the server address and $STATEGRAPH_API_KEY for the API key, as the CLI does. The others use the Stategraph Cloud address.

Authentication

Send a credential in the Authorization header as a bearer token: an API key, a session token, or an access token. A browser sends a session cookie instead.

API keys

An API key authenticates the stategraph CLI, Terraform, and API requests. Create one in the console under Settings > API Keys, or with POST /api/v1/user/access-tokens.

curl -H "Authorization: Bearer $STATEGRAPH_API_KEY" "$STATEGRAPH_API_BASE/api/v1/whoami"

Sign-in sets a session cookie, and the browser sends it automatically. Login with password also returns the session as a token for bearer authentication. There are three sign-in modes:

Access tokens

The GitHub and GitLab user and installation endpoints, stacks, the key-value store, the server configuration, and the refresh token endpoints take a short-lived access token. You get it in two steps:

  1. Create a refresh token with a name and the capabilities that it needs, with POST /api/v1/{vcs}/access-token. The response shows it one time. A refresh token is long-lived, and it can only get access tokens.
  2. Send the refresh token as the bearer token to POST /api/v1/access-token/refresh. In the examples, YOUR_API_KEY and your_api_key stand for the refresh token.
curl -X POST https://app.stategraph.cloud/api/v1/access-token/refresh \
  -H "Authorization: Bearer YOUR_API_KEY"

The response holds the access token. The access token has the capabilities that you selected for the refresh token.

{
  "token": "eyJhbGc..."
}

Send the access token on each request:

Authorization: Bearer YOUR_ACCESS_TOKEN
curl -X GET https://app.stategraph.cloud/api/v1/whoami \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

An access token expires after 60 seconds. This limits the damage when someone intercepts a token. To continue, call the refresh endpoint again for a new token. This script gets its first token, then a new one every 50 seconds, before the 60-second expiry:

#!/bin/bash
API_KEY="your_api_key_here"

get_access_token() {
  curl -s -X POST https://app.stategraph.cloud/api/v1/access-token/refresh \
    -H "Authorization: Bearer $API_KEY" | jq -r '.token'
}

ACCESS_TOKEN=$(get_access_token)

while true; do
  # Use the token
  curl -X GET https://app.stategraph.cloud/api/v1/whoami \
    -H "Authorization: Bearer $ACCESS_TOKEN"

  # Wait and refresh
  sleep 50
  ACCESS_TOKEN=$(get_access_token)
done

This Python client refreshes the token when it is 55 seconds old:

from datetime import datetime, timedelta

from requests import get, post

class StategraphClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.access_token = None
        self.token_expires_at = None
        self.base_url = "https://app.stategraph.cloud"

    def refresh_token(self):
        """Get a new access token"""
        response = post(
            f"{self.base_url}/api/v1/access-token/refresh",
            headers={"Authorization": f"Bearer {self.api_key}"}
        )
        self.access_token = response.json()["token"]
        # Set expiration to 55 seconds (5 second buffer)
        self.token_expires_at = datetime.now() + timedelta(seconds=55)

    def get_headers(self):
        """Get headers with valid access token"""
        if not self.access_token or datetime.now() >= self.token_expires_at:
            self.refresh_token()
        return {"Authorization": f"Bearer {self.access_token}"}

    def whoami(self):
        """Example API call"""
        response = get(
            f"{self.base_url}/api/v1/whoami",
            headers=self.get_headers()
        )
        return response.json()

client = StategraphClient("your_api_key")
user = client.whoami()

Keep credentials safe

  • Keep API keys and refresh tokens in environment variables or a secrets manager. Never commit them to version control.
  • Give each key only the capabilities that it needs.
  • Replace keys regularly: create new ones and delete the old ones.
  • Delete the keys that you do not use.

OpenAPI schema

GET /api/v1/openapi

Returns the OpenAPI schema of the API as JSON. It needs no authentication. Use it to discover endpoints and request and response types, and with tools that read OpenAPI specifications. The schema does not include the paths that start with /api/github, /api/v1/github, /api/v1/gitlab, /api/v1/user/github, /api/v1/access-token, or /api/v1/server.

curl "$STATEGRAPH_API_BASE/api/v1/openapi"

Response: 200 OK with the complete OpenAPI JSON schema. Each release also has the schema as a downloadable file.

Pagination

Many list endpoints use cursor-based pagination: limit sets the page size, and page takes the cursor. Request the first page:

curl "$STATEGRAPH_API_BASE/api/v1/tenants/$TENANT_ID/states?limit=50" \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"

Request the next page with the cursor from the previous response:

curl "$STATEGRAPH_API_BASE/api/v1/tenants/$TENANT_ID/states?limit=50&page=cursor..." \
  -H "Authorization: Bearer $STATEGRAPH_API_KEY"
GET /api/v1/github/installations/{installation_id}/repos?limit=50&page=...
GET /api/v1/gitlab/installations/{installation_id}/repos?limit=50&page=...

Some list endpoints take the cursor in cursor, from the next_cursor of the previous response. Others return the next page in a Link header. Each endpoint lists its parameters.

Filtering and sorting

Some list endpoints take a filter in q and a sort direction in d (asc or desc):

GET /api/v1/github/installations/{installation_id}/work-manifests?q=terraform&d=desc
GET /api/v1/gitlab/installations/{installation_id}/work-manifests?q=terraform&d=desc

The value of q depends on the endpoint: key:value filter terms for instances, and a search query for dirspaces and work manifests.

Errors

The API uses standard HTTP status codes:

Code Meaning
200 The request succeeded.
400 Bad request: invalid parameters.
401 Authentication is required. The response has no body.
403 Forbidden: the credential is missing or invalid, or it does not allow the request.
404 The resource does not exist.
429 Too many requests. See Rate limiting.
500 A server error occurred.

The body of a 400 depends on the endpoint. Many endpoints return a structured error:

{
  "id": "INVALID_DATE_PARAM",
  "data": "Error description"
}

id is a machine-readable error code, for example INVALID_DATE_PARAM or INVALID_GROUP_BY. Some validation failures, such as a malformed UUID when you create a state, return a 400 with an empty body. Check the status code first, and do not expect a JSON body on every 400.

Rate limiting

The API limits the request rate, to prevent abuse and keep usage fair. A request over the limit gets 429 Too Many Requests. Retry it with exponential backoff.

Endpoint pages

Page Endpoints
Users and sign-in The current user, their tenants, and their GitHub and GitLab users, login, logout, and first-time setup
User management Users, passwords, installation admins, and service accounts
Tenants and members Tenants, members and their roles, and invitations
API keys and access tokens API keys, default capabilities, group rules, refresh tokens, and access tokens
States States, the Terraform HTTP backend, instances, blast radius, modules, and summaries
Transactions and tasks Transactions, their logs, plan operations, and output, and long-running tasks
Queries and gap analysis SQL queries, the SQL schema, and the cloud resources that no state manages
Cost and billing Cost snapshots, history, attribution, plan-time cost, and billing sources
Security Security findings, scans, and the security impact of a transaction
VCS installations GitHub and GitLab installations: tenant links, repositories, pull requests, dirspaces, work manifests, groups, and webhooks
Stacks The stacks of a pull request
Key-value store Data that persists across workflow runs
Server The version, feature flags, and VCS configuration of the server, and the GitHub client ID

Next steps