Authentication

Authentication

Every call to the CHeKT API authenticates with a Bearer token. CHeKT supports three token sources in a progression from simple to strict — start where you are, move forward as your needs grow.

The three auth modes

Each mode is independent — you can mix them across apps in the same dealer. Most partners start with an API key and stay there forever. OAuth and Assertion Tokens come into play when you've outgrown the simpler model.

Client Credentials (OAuth 2.0)

Default · Most endpoints
Use for
Server-to-server calls from Dealer apps and Platform partners. Exchange client_id + client_secret at /oauth/token for an access_token.
Security
Short-lived bearer tokens, rotatable secret, optional IP allow list.
Complexity
Low — most SDKs include a built-in token helper.
Learn more →

Authorization access token

Connect endpoint
Use for
Only used to call PUT /partner/v1/dealers/connect — binds the dealer signed into the token to your external_dealer_id.
Security
User-authorized; the user must hold dealer-admin rights on the target dealer.
Complexity
Medium — requires the dealer admin's authorization flow.
Learn more →

Assertion Tokens

Highest-trust partners
Use for
Service-to-service partners with the strictest trust requirements. Replaces long-lived client secrets with signed JWTs.
Security
Highest — rotating signing keys, no long-lived secrets on disk.
Complexity
High — JWT signing, JWK publishing, token exchange.
Learn more →

Whatever the mode, the wire format is the same. Send a standard Authorization header with a Bearer-prefixed token. The token value is what differs by mode:

API key
Authorization: Bearer chekt_live_8h2j3kfm… — long-lived, per-app, dealer-scoped.
OAuth access token
Authorization: Bearer eyJhbGciOiJSUzI1NiIs… — short-lived (1h), refreshable, per-user.
Assertion-derived token
Authorization: Bearer eyJhbGciOiJFUzI1Ni… — short-lived (1h), obtained by exchanging a signed JWT.
HTTP
GET /v1/devices HTTP/1.1
Host: api.chekt.com
Authorization: Bearer chekt_live_8h2j3kfm2918sndlj…
Accept: application/json

API keys

API keys are the default. Every CHeKT App you create in the dealer portal gets exactly one active key. Keys are per-app, dealer-scoped, and rotatable.

Format
chekt_live_… (32+ characters). Prefix indicates environment: chekt_live_ for production, chekt_test_ for sandbox.
Scope
Limited to the app's granted permissions. The same key cannot exceed the scope set at app creation.
Lifetime
Indefinite. Keys do not expire. Rotate them yourself on the schedule that suits your risk model — quarterly is a good default.
Storage
Secret manager only. Never in code, environment files committed to git, or chat tools.

Storing the API key

Where to put your key, by platform. All five approaches are first-class; pick what matches your deployment.

shell
# Export from a sourced ~/.zshrc or ~/.bashrc
export CHEKT_API_KEY='chekt_live_…'

# Or load from .env via direnv
echo 'export CHEKT_API_KEY="chekt_live_…"' > .envrc
direnv allow

Rotation flow

Rotation is a one-click action in the dealer portal that issues a new key and keeps the old one valid for 24 hours so you can deploy without downtime.

Dealer admindealer.chekt.com
CHeKT
Developerships new key
  1. 01
    Dealer adminCHeKT
    Click Rotate keySettings → CHeKT Apps → [app] → Security
  2. 02
    CHeKTCHeKT
    Generate new key + old key 24h grace
  3. 03
    CHeKTDealer admin
    Show new key (once)
  4. 04
    Dealer adminDeveloper
    Hand off via secret manager1Password / Doppler / Vault
  5. 05
    DeveloperCHeKT
    Deploy + verify with new key
  6. 06
    Dealer adminCHeKT
    Click Revoke previous to end grace early (optional)
Figure 1. Rotating an API key without downtime. The 24h grace window covers most deploy pipelines.

OAuth 2.0 (preview)

For apps that act on behalf of end users — letting a dealer admin or operator sign in and approve scopes themselves — OAuth 2.0 is the right model. We're rolling this out in Q3 2026 for select partners.

Authorization code flow

The standard three-legged OAuth flow with PKCE. The end user signs in, grants scopes, and your app exchanges the code for tokens.

End useroperator or dealer
Your app
CHeKT auth
  1. 01
    End userYour app
    User clicks Connect to CHeKT
  2. 02
    Your appEnd user
    Redirect to CHeKT auth?client_id=…&scope=devices:read events:read
  3. 03
    End userCHeKT auth
    User signs in & approves scopes
  4. 04
    CHeKT authYour app
    Redirect back with ?code=…
  5. 05
    Your appCHeKT auth
    POST /oauth/token (exchange code)
  6. 06
    CHeKT authYour app
    Issue access_token + refresh_token
  7. 07
    Your appCHeKT auth
    Call API with Bearer <access_token>
Figure 2. OAuth 2.0 authorization-code flow with PKCE. The user is in the loop.
authorization_endpoint
https://auth.chekt.com/oauth/authorize — where you redirect the user.
token_endpoint
https://api.chekt.com/oauth/token — where you POST to exchange the code.
access_token TTL
One hour. Use the refresh token to get a new one without re-prompting the user.
refresh_token TTL
Thirty days, sliding window. Make any API call and the window resets.

Assertion Tokens (preview)

The strictest mode. Your service signs short-lived JWTs with a private key, exchanges them at CHeKT's token endpoint for access tokens, and rotates the signing key on its own schedule. No long-lived secret ever lives on disk.

Reserve Assertion Tokens for high-volume partners running in regulated environments — central stations with SOC 2 obligations, panel manufacturers shipping to regulated markets, large-scale automation platforms.

Partner service
CHeKT auth
CHeKT API
  1. 01
    Partner servicePartner service
    Sign a short-lived JWTHeader alg=ES256, exp=now+5min
  2. 02
    Partner serviceCHeKT auth
    POST /oauth/token (grant_type=jwt-bearer)
  3. 03
    CHeKT authPartner service
    Issue access_token (1h TTL)
  4. 04
    Partner serviceCHeKT API
    Call API with Bearer <access_token>
  5. 05
    Partner servicePartner service
    Rotate signing key quarterlyPublish new JWK; CHeKT picks it up
Figure 3. The assertion-token handshake. No long-lived secret ever lives on disk.
sign-jwt.ts
import { SignJWT, importJWK } from 'jose';

const privateJwk = JSON.parse(process.env.PARTNER_PRIVATE_JWK!);
const key = await importJWK(privateJwk, 'ES256');

const jwt = await new SignJWT({})
  .setProtectedHeader({ alg: 'ES256', kid: 'partner-key-1' })
  .setIssuer('your-partner-id')
  .setSubject('your-partner-id')
  .setAudience('https://api.chekt.com')
  .setIssuedAt()
  .setExpirationTime('5m')
  .sign(key);

// Exchange for an access token
const res = await fetch('https://api.chekt.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    assertion: jwt,
  }),
});

const { access_token } = await res.json();

IP allow lists

Layered on top of any auth mode, an IP allow list restricts API calls to known source IPs — your VPC NAT egress, your office, your CI runners. Requests from outside the list are rejected at the edge before reaching the API.

Common auth errors

The six errors that account for ~95% of auth-related calls to support.

401invalid_api_key

Token missing, malformed, or revoked. Confirm CHEKT_API_KEY is set and that the value matches what the dealer portal shows. If you rotated recently, the old key may have entered the 24h grace window and then been revoked.

401token_expired

OAuth or assertion-token access tokens expire after their TTL. Use the refresh token (OAuth) or sign a new JWT (assertion) to get a fresh access token.

401signature_invalid

Webhook HMAC verification failed. You're probably verifying against parsed JSON instead of raw bytes, or the signing secret rotated without your service redeploying.

403permission_denied

The token is valid but missing the scope this endpoint requires. Open the app's permissions in the dealer portal and grant the matching scope.

403ip_not_allowed

The request came from an IP outside the app's allow list. Add the source IP, or temporarily disable the allow list to confirm the rest of the call works.

429rate_limited

You're sending too many requests. Inspect the Retry-After header and back off with exponential jitter.

For the full error catalogue including non-auth codes, see the error reference. Every error response includes a request_id — log it.

Best practices

Next steps