> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chkk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

Welcome to the Chkk API — a REST interface that lets you query the Chkk resources surfaced by the Chkk Operational Safety Platform.

<Note>
  The v2 API is available to Enterprise customers only. Contact us to request access.
</Note>

## Pagination

Bulk‑fetch methods in Chkk use cursor‑based pagination. Two parameters control the flow:

| Parameter           | Type    | Default | Max  | Description                                         |
| ------------------- | ------- | ------- | ---- | --------------------------------------------------- |
| page\_size          | integer | 100     | 1000 | Maximum number of objects to return                 |
| continuation\_token | string  | —       | —    | Cursor pointing to where the next page should start |

Typical sequence

1. **First page** – Omit continuation\_token (and optionally set page\_size).
2. **Next page** – Pass the continuation\_token returned in the prior response.
3. **Done** – When the response no longer includes continuation\_token, you have reached the end.

Example

```
GET /risks?page_size=100&filter=cluster_id:k8scl_a1b2c3
  → 200 OK  
    {  
      "data": [ /* up to 100 RiskSummary objects */ ],  
      "continuation_token": "eyJ2IjoxLCJ..."  
    }

GET /risks?page_size=100&continuation_token=eyJ2IjoxLCJ...
  → 200 OK  
    {  
      "data": [ /* next set of RiskSummary objects */ ]  
    }
```

If page\_size exceeds 1000, Chkk returns HTTP 400 with an error object.

***

## Errors

Chkk uses standard HTTP response codes to indicate the outcome of an API request. Codes in the 2xx range indicate a successful request. Codes in the 4xx range indicate a client error, such as a missing required parameter. Codes in the 5xx range indicate a server error on Chkk’s end (these are uncommon).

| Code                    | Meaning                                                                      |
| ----------------------- | ---------------------------------------------------------------------------- |
| `200 OK`                | Request succeeded.                                                           |
| `400 Bad Request`       | Malformed filters, missing parameters.                                       |
| `401 Unauthorized`      | Missing/invalid bearer token.                                                |
| `404 Not Found`         | Resource does not exist or is out of scope.                                  |
| `429 Too Many Requests` | Rate-limit exceeded. Retrying after the `Retry-After` header is recommended. |
| `5xx`                   | Temporary service error on Chkk’s side.                                      |

## API Usage Examples

The Go program below:

1. Calls **`GET /risks`** to collect every detected Risk instance in a cluster.
2. For each Risk, calls **`GET /risks/{id}/resources`** to enumerate affected resources.
3. **Optionally** shows **only** the resources that live in the namespace you pass with `-namespace`.
4. Prints output to `stdout` and writes a CSV file called `risks.csv` to the current working directory with the output.

### Prerequisites

| Requirement     | Notes                                                               |
| --------------- | ------------------------------------------------------------------- |
| Go 1.20 +       | No third-party dependencies.                                        |
| Outbound HTTPS  | Script contacts `https://api.us.chkk.io/v1`                         |
| AWS credentials | Script uses AWS STS to generate a presigned URL for authentication. |

### Example script

```go theme={"dark"}
package main

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/pkg/errors"
)


type loginResponse struct {
	AccessTokens map[string]map[string]struct {
		AccessToken string `json:"access_token"`
	} `json:"access_tokens"`
}

type riskSummary struct {
	ID        string `json:"id"`
	Signature struct {
		ID string `json:"id"`
	} `json:"signature"`
}

type listRisksResponse struct {
	Data []riskSummary `json:"data"`
}

type riskResource struct {
	Kind      string `json:"kind"`
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

type listRiskResourcesResponse struct {
	Data []riskResource `json:"data"`
}

var (
	clusterID  = flag.String("cluster-id", "", "Chkk cluster ID (required)")
	namespace  = flag.String("namespace", "", "Namespace filter (optional)")
	outFile    = flag.String("out", "risks.csv", "CSV output filename")
	apiBase    = "https://api.us.chkk.io/v1"
	httpClient = &http.Client{Timeout: 15 * time.Second}
)

func main() {
	flag.Parse()
	if *clusterID == "" {
		exitErr(errors.New("flag -cluster-id is required"))
	}

	ctx := context.Background()

	token, err := authenticate(ctx)
	if err != nil {
		exitErr(err)
	}

	risks, err := listRisks(ctx, token, *clusterID)
	if err != nil {
		exitErr(err)
	}

	if err := printSummary(ctx, token, risks); err != nil {
		exitErr(err)
	}

	if err := writeCSV(ctx, token, risks); err != nil {
		exitErr(err)
	}
}

func printSummary(ctx context.Context, token string, risks []riskSummary) error {
	for _, rs := range risks {
		res, err := listRiskResources(ctx, token, rs.ID)
		if err != nil {
			return err
		}

		count := filterByNamespace(res, *namespace)
		if *namespace != "" {
			fmt.Printf("%s, %s: %d affected resources in namespace %s\n", *clusterID, rs.Signature.ID, count, *namespace)
		} else {
			fmt.Printf("%s, %s: %d affected resources\n", *clusterID, rs.Signature.ID, count)
		}
	}
	return nil
}

func writeCSV(ctx context.Context, token string, risks []riskSummary) error {
	file, err := os.Create(*outFile)
	if err != nil {
		return errors.Wrap(err, "create CSV file")
	}
	defer file.Close()
	cw := csv.NewWriter(file)
	defer cw.Flush()

	if err := cw.Write([]string{"Cluster", "SignatureID", "Kind", "Name", "Namespace"}); err != nil {
		return errors.Wrap(err, "write CSV header")
	}

	for _, rs := range risks {
		resources, err := listRiskResources(ctx, token, rs.ID)
		if err != nil {
			return err
		}

		for _, r := range resources {
			if *namespace != "" && r.Namespace != *namespace {
				continue
			}
			if err := cw.Write([]string{
				*clusterID,
				rs.Signature.ID,
				r.Kind,
				r.Name,
				r.Namespace,
			}); err != nil {
				return errors.Wrap(err, "write CSV row")
			}
		}
	}
	return nil
}

func filterByNamespace(resources []riskResource, ns string) int {
	if ns == "" {
		return len(resources)
	}
	count := 0
	for _, r := range resources {
		if r.Namespace == ns {
			count++
		}
	}
	return count
}

func authenticate(ctx context.Context) (string, error) {
	url, err := presignSTS(ctx)
	if err != nil {
		return "", errors.Wrap(err, "generate presigned STS URL")
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiBase+"/login", nil)
	if err != nil {
		return "", errors.Wrap(err, "construct login request")
	}
	req.Header.Set("Authorization", "AWS "+url)

	resp, err := httpClient.Do(req)
	if err != nil {
		return "", errors.Wrap(err, "perform login request")
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		data, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", errors.Wrap(err, "failed to read response body")
		}
		return "", errors.Errorf("Received Login Error. Code: %d Body: %s", resp.StatusCode, string(data))
	}

	var lr loginResponse
	if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
		return "", errors.Wrap(err, "decode login response")
	}

	for _, acct := range lr.AccessTokens {
		for _, bundle := range acct {
			return bundle.AccessToken, nil
		}
	}
	return "", errors.New("no access tokens returned")
}

func listRisks(ctx context.Context, token, cluster string) ([]riskSummary, error) {
	url := fmt.Sprintf("%s/risks?filter=cluster_id:%s", apiBase, cluster)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, errors.Wrap(err, "construct list risks request")
	}

	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "perform list risks request")
	}
	defer resp.Body.Close()

	var lr listRisksResponse
	if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
		return nil, errors.Wrap(err, "decode list risks response")
	}
	return lr.Data, nil
}

func listRiskResources(ctx context.Context, token, riskID string) ([]riskResource, error) {
	url := fmt.Sprintf("%s/risks/%s/resources", apiBase, riskID)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, errors.Wrap(err, "construct list risk resources request")
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "perform list risk resources request")
	}
	defer resp.Body.Close()

	var r listRiskResourcesResponse
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil, errors.Wrap(err, "decode list risk resources response")
	}
	return r.Data, nil
}

func presignSTS(ctx context.Context) (string, error) {
	cfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		return "", errors.Wrap(err, "load AWS config")
	}
	cfg.Region = "us-east-1"

	stsClient := sts.NewFromConfig(cfg)
	sign := sts.NewPresignClient(stsClient)
	identity, err := sign.PresignGetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
	if err != nil {
		return "", errors.Wrap(err, "failed to create presigned URL")
	}
	return identity.URL, nil
}

func exitErr(err error) {
	fmt.Fprintf(os.Stderr, "ERROR: %+v\n", err)
	os.Exit(1)
}
```
