Using the API to create a new scanning configuration is a five step journey.
All request to the API require a special authentication token. You'll need to create one. Create a normal
tfstate.com account and then navigate to the Profile page. Towards the bottom there'll be a section to create
the API authentication token.
The page will briefly display both your Organization ID and your Authentication Token. Copy them down, you'll
need them to proceed.
Actions
Register a public GitHub repository
Register a private GitHub repository
Register a public Gitlab repository
Register a private Gitlab repository
List all registered repositories
Un-register a repository
Get the details of a repository
Test the connection to the respository
Description
This request registers one of your public GitHub repositories with tfstate.com. The repository should contain Terraform *.tf files which we
can examine against your infrastructure.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"save": True,
# "state_id": "(to edit existing instead of create)",
"state_repo": "https://github.com/my-account/my-repo",
"state_type": "github-public"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"message": "Saved GitHub public repo: my-account/my-repo",
"state_id": "89d5d244c9104294aa3c419b2b76250a"
}
Description
This request registers one of your private GitHub repositories with tfstate.com. The repository should contain Terraform *.tf files which we
can examine against your infrastructure.
When using the tfstate.com API to register private GitHub repositories a GitHub Personal Access Token must be used. You can create one by navigating
to:
GitHub.com -> Settings -> Developer Settings (at the bottom) -> Personal Access Tokens (classic)
Ensure the Personal Access Token you create has the Repo permission associated with it.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"save": True,
# "state_id": "(to edit existing instead of create)",
"state_repo": "https://github.com/my-account/my-repo",
"state_type": "github-private",
"state_token": "github personal access token"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"message": "Saved GitHub private repo: my-account/my-repo",
"state_id": "89d5d244c9104294aa3c419b2b76250a"
}
Description
This request registers one of your public Gitlab repositories with tfstate.com. The repository should contain Terraform *.tf files which we
can examine against your infrastructure.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"save": True,
# "state_id": "(to edit existing instead of create)",
"state_repo": "https://gitlab.com/my-account/my-repo",
"state_type": "gitlab-public"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"message": "Saved Gitlab public repo: my-account/my-repo",
"state_id": "89d5d244c9104294aa3c419b2b76250a"
}
Description
This request registers one of your private Gitlab repositories with tfstate.com. The repository should contain Terraform *.tf files which we
can examine against your infrastructure.
You will need to login to Gitlab and create an access token. The access token can be a User access token, or a Project access token.
Note the permissions
associated with the access token will need at least read_api and read_repository. If you want this configuration to be able to create Gitlab
issues, then you should grant the token the permission of api instead.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"save": True,
# "state_id": "(to edit existing instead of create)",
"state_repo": "https://gitlab.com/my-account/my-repo",
"state_type": "gitlab",
"state_token": "gitlab access token"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"message": "Saved Gitlab repo: my-account/my-repo",
"state_id": "89d5d244c9104294aa3c419b2b76250a"
}
Description
This request retrieves a list of all the repositories you have registered with tfstate.com.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"list": True
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
"items": [
{
id: "b7e4bce3fb8d48b38ac7aaacccbbbsss"
full_name: "my-account/my-repo"
repo: "https://github.com/my-account/my-repo"
type: "github"
},
{
...
}
]
}
Description
This request removes a repository configuration.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"delete": True,
"state_id": "b7e4bce3fb8d48b38ac7aaacccbbbsss"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"message": "Deleted configuration for repo"
}
Description
This request retrieves some basic details about a repository configuration. You can obtain the state_id field
from either performing a list call, or in the payload that is provided after a save call.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"get": True,
"state_id": "b7e4bce3fb8d48b38ac7aaacccbbbsss"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"payload": {
"id": "b7e4bce3fb8d48b38ac7aaacccbbbsss",
"type": "github-public",
"org_id": "f6d42cebc2c142a899e5dfd2e20cd33b",
"name_of_thing": "state",
"repo_id": "12343242",
"repo": "https://github.com/my-account/my-repo",
"full_name": = "my-account/my-repo",
"private": False
}
}
Description
This request attempts to perform a checkout of the repository. If the
test succeeds then it returns a list of the git branches and terraform folders
that it finds. You can obtain the state_id field from either performing a
list call, or in the payload that is provided after a save call.
POST https://api.tfstate.com/state
import requests
url = 'https://api.tfstate.com/state'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"test": True,
"state_id": "b7e4bce3fb8d48b38ac7aaacccbbbsss"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"branches": [
"main",
"feature-branch-1",
"feature-branch-2"
],
"files": [
"terraform-folder1",
"terraform-folder2",
"terraform-folder3"
]
}
Actions
Connect AWS IAM Role
Connect AWS IAM User
Connect GCP WIF Role
Connect GCP Service Account
Connect Custom Provider
List connections
Remove a connection
Get the details of a connection
Test the provider connection
Description
This request registers an AWS IAM Role connection to your infrastructure.
You must first create an IAM Role. There is a Terraform file here
that will help create it for you: make-role.tf.
Once the IAM Role has been created, obtain the infra_role field used below by running: terraform output role.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
# the Role name MUST match pattern tfstatecom*
payload = {
"save": True,
# "infra_id": "(to edit existing instead of create)",
"infra_type": "aws-role",
"infra_name": "Prod AWS Role connection",
"infra_role": "[terraform output role] eg: arn:aws:iam::999999999:role/tfstatecom-special-role"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"infra_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers an AWS IAM User Key connection to your infrastructure.
You must first create an IAM User. There is a Terraform file here
that will help create it for you: make-key.tf.
Once the IAM User has been created, obtain the Key ID and the Key Secret used below by running: terraform output key_id and terraform output key_secret.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
# the Role name MUST match pattern tfstatecom*
payload = {
"save": True,
# "infra_id": "(to edit existing instead of create)",
"infra_type": "aws",
"infra_name": "Prod AWS User connection",
"infra_key_id": "[terraform output key_id]",
"infra_key_secret": "[terraform output key_secret]"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"infra_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers a GCP Workload Identity Federation connection to your infrastructure.
There is a Terraform file here
that will help create it for you: make-gcp-role-service-account.tf.
Once that has been created, obtain the JSON credentials by running: terraform output -raw service_account_credentials_json.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
# you can obtain the workload JSON by running:
# terraform output -raw service_account_credentials_json
workload = '''
{
"audience": "//iam.googleapis.com/projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"regional_cred_verification_url": "https://sts.ap-southeast-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/USERNAME@PROJECT_NAME.iam.gserviceaccount.com:generateAccessToken",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"type": "external_account",
"universe_domain": "googleapis.com"
}
'''
payload = {
"save": True,
# "infra_id": "(to edit existing instead of create)",
"infra_type": "gcp-role",
"infra_name": "Prod GCP WIF connection",
"infra_workload": workload
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"infra_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers a GCP Service Account connection to your infrastructure.
There is a Terraform file here
that will help create it for you: make-gcp-service-account.tf.
Once that has been created, obtain the JSON credentials by running: terraform output -raw service_account_key_json.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
# you can obtain the workload JSON by running:
# terraform output -raw service_account_key_json
credentials = '''
{
"type": "service_account",
"project_id": "my-project-name",
"private_key_id": "c8258a...",
"private_key": "-----BEGIN PRIVATE KEY...[OMITTED]",
"client_email": "my-username@something...",
"client_id": "1012...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509...",
"universe_domain": "googleapis.com"
}
'''
payload = {
"save": True,
# "infra_id": "(to edit existing instead of create)",
"infra_type": "gcp",
"infra_name": "Prod GCP SA connection",
"infra_credentials": credentials
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"infra_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers a custom connection to any provider, using environment variables of your choosing.
For example to authenticate against Cloudflare you would set the variable CLOUDFLARE_API_TOKEN.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
env_vars = '''
CLOUDFLARE_API_TOKEN="secret token goes here"
SOME_OTHER_VAR="another var"
'''
payload = {
"save": True,
# "infra_id": "(to edit existing instead of create)",
"infra_type": "oth",
"infra_name": "Cloudflare connection",
"infra_credentials": env_vars
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"infra_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request retrieves previously registered connections.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"list": True
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"items": []
}
Description
This request removes a previously registered connection.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"delete": True,
"infra_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}
Description
This request retrieves the details of a previously registered connection. Note: any sensitive keys, passwords or credentials
are not returned.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"get": True,
"infra_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"payload": {
...
}
}
Description
This request tests a previously registered connection. For example if a
supplied credential was not correct then this test endpoint would help to
diagnose that.
POST https://api.tfstate.com/infra
import requests
url = 'https://api.tfstate.com/infra'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"test": True,
"infra_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}
Actions
Configure POST action
Configure email action
Configure GitHub Issue action
Configure Gitlab Issue action
List action configurations
Remove an action configuration
Get the details of an action configuration
Test the action configuration
Description
This request registers a custom POST request action.
Setup a configuration that triggers a POST request to anywhere you like, when configuration drift is detected.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
request_headers = '''
Content-type: application/json
Authorization: Bearer abcd1234
'''
request_body = '''
{
"repo": "REPO",
"folder": "FOLDER",
"severity": "AI_SEVERITY",
"results": "OUTPUT",
"date": "DATE",
"time": "TIME",
"timezone": "TZ",
"snooze_link": "SNOOZE_4",
"contact": "ADMIN_EMAILS"
}
'''
payload = {
"save": True,
# "alert_id": "(to edit existing instead of create)",
"alert_type": "other",
"alert_name": "POST request to my internal alerting tool",
"alert_url": "https://somewhere.example.com/webhook",
"alert_headers": request_headers,
"alert_body": request_body
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"alert_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers an email send action.
Setup a configuration that triggers a custom email to be sent anywhere you like, when configuration drift is detected.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
alert_body = '''
This is the tfstate.com alerting system.
Some terraform configuration drift has been detected in the following locations:
DATE TIME TZ
repo link: https://github.com/REPO
folder: FOLDER
AI_SUMMARY
OUTPUT
Just reach out to ADMIN_EMAILS if problems.
Snooze this alert for 3 days: SNOOZE_3
'''
payload = {
"save": 1,
# "alert_id": "(to edit existing instead of create)",
"alert_type": "email",
"alert_name": "Work email address",
"alert_recipients": "test@alexlance.com",
"alert_subject": "tfstate.com alert subject",
"alert_body": alert_body
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"alert_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers a GitHub Issue creation.
Setup a configuration that triggers a GitHub Issue to be created, when configuration drift is detected.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
alert_body = '''
Configuration drift detected in FOLDER.
Description: AI_SUMMARY
AI_DESCRIPTION
```
OUTPUT_BRIEF
```
'''
payload = {
"save": 1,
# "alert_id": "(to edit existing instead of create)",
"alert_type": "github-issue",
"alert_name": "GitHub issue template #1",
"alert_subject": "Configuration drift detected in FOLDER",
"alert_body": alert_body
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"alert_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request registers a Gitlab Issue creation.
Setup a configuration that triggers a Gitlab Issue to be created, when configuration drift is detected.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
alert_body = '''
Configuration drift detected in FOLDER.
Description: AI_SUMMARY
AI_DESCRIPTION
```
OUTPUT_BRIEF
```
'''
payload = {
"save": 1,
# "alert_id": "(to edit existing instead of create)",
"alert_type": "gitlab-issue",
"alert_name": "Gitlab issue template #1",
"alert_subject": "Configuration drift detected in FOLDER",
"alert_body": alert_body
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"alert_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request retrieves all the alerting/action configurations.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"list": True
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
}
Description
This request removes an alerting/action configuration.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"delete": 1,
"alert_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
}
Description
This request retrieves an alerting/action configuration.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"get": 1,
"alert_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"payload": {
...
},
}
Description
This request tests an alerting/action configuration, by firing off a test event to the configured action.
POST https://api.tfstate.com/alert
import requests
url = 'https://api.tfstate.com/alert'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"test": 1,
"alert_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}
Actions
Create a scanning schedule configuration
Activate/Deactivate a scanning schedule configuration
List scanning schedule configurations
Delete a scanning schedule configuration
Retrieve a scanning schedule configuration
Description
This request uses previously created requests to /state, /infra and /alert and joins them all together into
a scanning schedule configuration. This configuration runs the scans according to the schedule you setup here.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"save": 1,
# "config_id": "(to edit existing instead of create)",
"state": "b7e3ace3fb8d48b38ac799b83179741a",
"infra": "4d2b0617e7b64bdb8aceb94e03a24d99",
"alert": "8e3dfe424f6a48978f9c0f58b40543e0",
"branch": "main",
"alert_tweak_1": 1,
"alert_tweak_2": false,
"alert_tweak_3": false,
"frequency": "hourly",
"frequency_offset": "22",
"state_files": [
"folder1",
"folder2"
]
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"config_id": "99d5d255c9104294aa3c419b2b76250a"
}
Description
This request activates or deactivates a scanning schedule configuration.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"activate": True,
"onoff": True,
"config_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}
Description
This request retrieves the scanning schedule configurations.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"list": True
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good",
"items": [
{
"id":
"state":
"state_label":
"infra":
"infra_label":
"alert":
"alert_label":
"alert_tweaks":
"branch":
"branches":
"enabled_files":
"snoozed":
"frequency":
"frequency_label":
"frequency_offset":
"price":
"paid":
"active":
"starting":
"created":
"timezone":
},
{
...
}
]
}
Description
This request deletes a scanning schedule configuration.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"delete": True,
"config_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}
Description
This request retrieves a scanning schedule configuration.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"get": True,
"config_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
"payload": {}
}
Description
This request runs a one-off scan based on the configured parameters for a scanning schedule configuration.
POST https://api.tfstate.com/config
import requests
url = 'https://api.tfstate.com/config'
headers = {
"X-Api-Key": "...",
"X-Org-ID": "..."
}
payload = {
"test": True,
"config_id": "..."
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Returns
{
"status": "good"
}