Clariti Docs

API Keys & Bulk Export

An API key lets a script or another tool talk to Clariti without a person signing in. The most common use is bulk export: pulling every asset and finding into your SIEM, a BI dashboard, a spreadsheet, or a nightly backup.

This page is the reference for the public API. The production API does not serve an interactive explorer (/docs, /redoc and /openapi.json are turned off), so everything you need is here.

Create an API Key

Only workspace admins can create or revoke keys.

  1. In Clariti, open Settings → API keys (under the Workspace group).
  2. Click Create API key.
  3. Give it a Name that says what uses it, such as SIEM export or Nightly backup.
  4. Choose an Access level (see below). Pick the narrowest one that does the job.
  5. Click Create key, then Copy the key and store it in your password manager or secrets vault.

The key is shown once. Clariti keeps only a hash of it, so nobody (including Clariti support) can show it to you again. If you lose it, revoke it and create a new one.

Keys look like clk_1a2b3c4d5e6f_.... The part after clk_ up to the next underscore is a public prefix: it is what you see in the key list, so you can tell keys apart without exposing the secret. The key list also shows when each key was created and last used.

A workspace can have up to 25 active keys. Revoke keys you no longer use.

Access Levels

AccessCan doCannot do
Export onlyGET /api/v1/export/*: download assets and findingsAnything else, including other read endpoints
Read onlyGET requests on your workspace dataAny change (POST, PATCH, PUT, DELETE)
Read & writeRead and change workspace data, e.g. triage findingsManage the workspace

No key, whatever its access level, can call /api/v1/admin/*, /api/v1/billing/* or /api/v1/oauth/*. A key can never add members, create or revoke other keys, change billing, or connect integrations. Those need a signed-in admin.

For a SIEM or reporting feed, use Export only.

When a Key Stops Working

A key acts on behalf of the admin who created it, and every action it takes is attributed to that admin. It stops working when:

  • You revoke it in Settings → API keys. The next request fails with 401.
  • Its creator is disabled. Requests fail with 403 until the admin is re-enabled.
  • Its creator is removed from the workspace. Their keys are deleted with them, and requests fail with 401.

Before you remove an admin, check the key list for keys they created and replace any that are still in use.

Authentication

Send the key as a bearer token (Authorization: Bearer clk_...) on every request to https://api.clariti-ai.xyz. With your key in the CLARITI_API_KEY environment variable:

curl -sS "https://api.clariti-ai.xyz/api/v1/export/findings?limit=10" \
  -H "Authorization: Bearer $CLARITI_API_KEY"

Keep keys out of scripts and source control. Load them from an environment variable or a secrets manager instead, as this example and the ones below do.

Export Endpoints

EndpointReturns
GET /api/v1/export/assetsEvery asset record (devices, users, software, cloud and network assets), one per source: the same laptop seen by two tools appears twice
GET /api/v1/export/findingsEvery finding

Query Parameters

ParameterValuesDefaultNotes
formatjsonl or csvjsonlJSONL is one JSON object per line
limit1 to 50001000Rows per page
cursorValue of the previous page's X-Next-CursornoneLeave it out for the first page

Each request returns one page, oldest record first. The response body is the page itself:

  • format=jsonl returns application/x-ndjson. List fields are JSON arrays and missing values are null.
  • format=csv returns text/csv with a header row on every page. List fields are joined with ;, object fields are compact JSON, booleans are true or false, and any cell starting with =, +, -, @, a tab or a carriage return is prefixed with ' so spreadsheets don't run it as a formula.

An empty workspace returns 200 with an empty body.

Pagination

Paging information comes back in response headers, not in the body:

HeaderMeaning
X-Has-Moretrue if there is another page, false on the last page
X-Next-CursorPass this as cursor to get the next page. Empty on the last page

Keep requesting with the new cursor until X-Has-More is false. Records created while you are paging show up on a later page rather than shifting earlier ones, so you won't get duplicates or gaps.

Pages are ordered by when a record was created, so a cursor only moves forward. A finding that changes status after you exported it won't reappear on a later page. To capture changes, run a fresh export from the first page (for example, nightly) rather than resuming from an old cursor.

Example: curl and bash

Exports every finding to findings.jsonl:

#!/usr/bin/env bash
set -euo pipefail

API="https://api.clariti-ai.xyz/api/v1/export/findings"
KEY="${CLARITI_API_KEY:?set CLARITI_API_KEY first}"
OUT="findings.jsonl"
cursor=""

: > "$OUT"
while true; do
  curl -sS --fail-with-body -G "$API" \
    -H "Authorization: Bearer $KEY" \
    --data-urlencode "format=jsonl" \
    --data-urlencode "limit=5000" \
    ${cursor:+--data-urlencode "cursor=$cursor"} \
    -D headers.txt -o page.jsonl
  cat page.jsonl >> "$OUT"

  has_more=$(grep -i '^x-has-more:' headers.txt | tr -d '\r' | awk '{print $2}')
  cursor=$(grep -i '^x-next-cursor:' headers.txt | tr -d '\r' | awk '{print $2}')
  [ "$has_more" = "true" ] || break
done

rm -f headers.txt page.jsonl
echo "Wrote $(wc -l < "$OUT") findings to $OUT"

Change findings to assets in API (and the file name) to export assets. --fail-with-body needs curl 7.76 or later; it makes the script stop on an error instead of saving the error as data.

Example: Python

Uses the requests library (pip install requests). Handles both formats, waits and retries when rate-limited, and keeps only the first CSV header row:

import os
import time

import requests

API = "https://api.clariti-ai.xyz/api/v1/export"
KEY = os.environ["CLARITI_API_KEY"]


def export(resource: str, path: str, fmt: str = "jsonl", limit: int = 5000) -> int:
    """Write every page of /export/<resource> to one file. Returns the page count."""
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {KEY}"
    cursor = None
    pages = 0

    with open(path, "w", encoding="utf-8", newline="") as out:
        while True:
            params = {"format": fmt, "limit": limit}
            if cursor:
                params["cursor"] = cursor
            resp = session.get(f"{API}/{resource}", params=params, timeout=60)

            if resp.status_code == 429:
                time.sleep(int(resp.headers.get("Retry-After", "60")))
                continue
            if not resp.ok:
                try:
                    err = resp.json()["error"]
                    detail = f"{err['code']}: {err['message']}"
                except (ValueError, KeyError):
                    detail = resp.text[:200]
                raise RuntimeError(f"HTTP {resp.status_code} {detail}")

            resp.encoding = "utf-8"
            body = resp.text
            if fmt == "csv" and pages > 0:
                body = body.split("\n", 1)[1] if "\n" in body else ""  # drop repeated header
            out.write(body)
            pages += 1

            if resp.headers.get("X-Has-More") != "true":
                return pages
            cursor = resp.headers["X-Next-Cursor"]


if __name__ == "__main__":
    export("assets", "assets.csv", fmt="csv")
    export("findings", "findings.jsonl")

Columns

The export columns are a fixed allow-list, in this order. CSV header rows use exactly these names, and JSONL objects use the same keys. Raw vendor payloads are never included.

Assets

ColumnDescription
idClariti asset ID
sourceIntegration the record came from
source_idThe asset's ID in that integration
asset_typeDEVICE, USER, SOFTWARE, CLOUD, NETWORK or OTHER
hostnameHostname
os_nameOperating system
os_versionOperating system version
serial_numberHardware serial number
ip_addressesIP addresses (list)
mac_addressesMAC addresses (list)
statusACTIVE, STALE or DECOMMISSIONED
ownerAssigned owner
departmentDepartment
business_criticalityCRITICAL, HIGH, MEDIUM, LOW or UNKNOWN
tagsTags (list)
managed_byTools managing the asset (list)
software_countNumber of installed software packages
quality_scoreHow complete the record is, from 0 to 1
site_idSite the asset belongs to
first_seenFirst seen (ISO 8601 timestamp)
last_seenLast seen (ISO 8601 timestamp)
created_atWhen Clariti first recorded it (ISO 8601 timestamp)

Findings

ColumnDescription
idClariti finding ID
finding_typecoverage_gap, drift, anomaly, stale_asset, config_risk or vulnerability
severitycritical, high, medium, low or info
statusopen, in_progress, resolved, accepted_risk or false_positive
titleShort title
descriptionFull description
sourceWhat raised it: correlation, configcheck, hygiene, manual, integration or intel
source_refDetails that tie the finding back to its source (an object; see below)
affected_asset_idsIDs of affected assets (list; matches the assets export id)
cvss_scoreCVSS score
epss_scoreEPSS exploit probability
epss_percentileEPSS percentile
kev_listedtrue if on CISA's Known Exploited Vulnerabilities list
due_dateRemediation due date
assigned_atWhen it was assigned
resolved_atWhen it was resolved
created_atWhen it was created
updated_atWhen it last changed
last_activity_atLast activity on it
exposure_categoryThe exposure score category it counts in, for example exploited_vulns or identity_access
exposure_pointsHow much exposure it adds to that category
projected_liftRoughly how many score points (0-100) fixing it recovers, once the fix is verified

All timestamps are ISO 8601. Empty values are blank in CSV and null in JSONL. kev_listed is true or false in both formats.

The three exposure columns are empty for a finding that does not count toward the exposure score (for example a stale-asset or alert finding, or one marked false positive). projected_lift is an estimate: after a fix is verified, the recalculated score is the real number. New columns are always added at the end, so existing column positions do not change.

source_ref is a JSON object in JSONL. In CSV the cell holds the same object as compact JSON with its keys sorted (for example {"check_id":"mfa_disabled","run_id":"..."}), so any JSON parser can read it.

Errors

Every error uses the same JSON envelope:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "This API key can only call the export endpoints",
    "details": {}
  }
}

Branch on code, not on message: messages are written for people and may be reworded. details holds extra context when there is any, such as the list of invalid fields on a 422.

StatusCodeCommon causes
401UNAUTHORIZEDMissing Authorization header, a mistyped key, a revoked key, or the key's creator was removed
403FORBIDDENThe key's access level doesn't allow this request, the route is off-limits to all keys (/admin, /billing, /oauth), or the key's creator is disabled
422VALIDATION_ERRORA bad query parameter, e.g. format=xml or limit=10000
429RATE_LIMITEDToo many requests. Wait for the number of seconds in Retry-After, then retry

A cursor that has been edited or truncated returns 400 with code VALIDATION_ERROR. Start again from the first page.

Rate Limits

Each workspace can make 100 requests per minute to each endpoint. The limit counts the endpoint and HTTP method together, so GET /api/v1/export/assets and GET /api/v1/export/findings each have their own 100.

The limit is per workspace, not per key. All of your keys, and everyone using the Clariti app in your workspace, share it. Responses include X-RateLimit-Limit and X-RateLimit-Remaining so you can see how close you are.

When you go over, you get 429 RATE_LIMITED with a Retry-After: 60 header. Wait that long and retry. With limit=5000, exporting 50,000 findings takes 10 requests, so a typical SMB export never gets near the limit.

Security Tips

  • Give each tool its own key, so revoking one doesn't break the others.
  • Use Export only unless the tool really needs more.
  • Store keys in a secrets manager or environment variable, never in a script or a shared document.
  • Check Last used in the key list. A key nobody has used in months is a key to revoke.
  • If a key may have leaked, revoke it right away and create a new one. Revoking takes effect on the next request.