Credentials & Security
CheckUpstream issues three kinds of credentials, each built for exactly one purpose. Pick the right one and the SDKs, REST API, and MCP server will refuse to accept any other type — so you can't accidentally ship a secret to the browser or paste a publishable key into a CI script.
The three credentials at a glance
| Credential | Prefix | Where it lives | What it does | Is it secret? |
|---|---|---|---|---|
| SDK Key | cup_sdk_ | Server, browser, mobile SDK bundles | Sends telemetry to the ingest endpoint | No — safe to ship in client bundles |
| API Token | cup_api_ | Servers, CI, curl, Terraform, MCP env vars | Reads from the REST API and MCP tools | Yes — treat like a password |
| AI Connection | n/a (OAuth) | Claude Desktop, Cursor, any MCP client | Authorizes an AI tool via OAuth 2.1 + PKCE | Short-lived; refresh handled for you |
The names and prefixes are load-bearing. Every SDK, server endpoint, and settings screen uses them in exactly one sense, so reading a snippet like cup_sdk_abc123 is enough to know it's safe to commit to source.
Rule of thumb. If you're writing browser, mobile, or frontend code, you want an SDK Key. If you're scripting against the REST API from a backend service or CI job, you want an API Token. If you're connecting an AI tool that speaks MCP, you want an AI Connection (OAuth).
SDK Keys — publishable, write-only telemetry credentials
SDK Keys authenticate the CheckUpstream SDKs so they can send telemetry to https://ingest.checkupstream.com/v1/events. They are designed to be public — we expect them to appear in browser bundles, source repositories, mobile app binaries, and public CI configs. The security model does not depend on keeping them secret.
What an SDK Key can do:
POSTtelemetry events to the ingest endpoint, scoped to one project
What an SDK Key cannot do:
- Read anything from the REST API
- Call MCP tools
- Read your dependency graph, incidents, runbooks, or team members
- Access a different project, even in the same org
Safe-by-default mechanics:
- Project scope — every SDK Key is bound to one project. A leak only affects that project's telemetry.
- Origin allowlist — you can optionally restrict an SDK Key to specific browser origins (
https://app.example.com). The ingest endpoint enforces the allowlist by checking theOriginheader. - Rate limits — per-key and per-origin, so a bad actor can't flood the endpoint.
- Revocable in one click — rotate from Settings → Credentials → SDK Keys.
Creating an SDK Key
- Go to Settings → Credentials → SDK Keys.
- Click Generate SDK Key.
- Pick a project (required) and optionally an allowed-origins list for production.
- Copy the key — you'll see the full value once; after that we only store the hash.
import { checkupstream } from "@checkupstream/sdk";
checkupstream.init({
sdkKey: "cup_sdk_a1b2c3d4e5f6...",
});If you accidentally pass an API Token (cup_api_*) where an SDK Key is expected, both TS SDKs refuse to initialise and log a refusing to initialise: sdkKey looks like an API Token error. This catches the worst class of key mix-ups before a single request goes out.
API Tokens — secret, scoped, expiring
API Tokens are the credential for programmatic access to CheckUpstream. REST API calls, CI scripts, curl commands, and Terraform providers all use API Tokens.
They're secret. Treat them like a database password: never commit them to source, never put them in browser bundles, never paste them into public chat. If one leaks, revoke it and generate a new one.
What an API Token can do (subject to its scopes):
- Read or mutate any org-scoped resource via
/api/v1/* - Manage projects, incidents, alerts, runbooks
Not for MCP. MCP clients (Claude Desktop, Claude Code, Cursor, …) authenticate via OAuth 2.1 + PKCE against the remote MCP transport — no API token needed. See MCP Setup.
Scopes:
| Scope | Grants |
|---|---|
read | All GET endpoints across the org |
write | GET + mutation endpoints (create/update) |
admin | Full access, including billing and team management |
Lifecycle:
- Required expiration — defaults to 90 days. Maximum 365. You can opt into a non-expiring token, but the UI makes you confirm it deliberately.
- Rotation with grace period — rotating an API Token issues a new one and gives the old one a 24-hour grace window so long-running services don't drop requests.
- Revoke — revocation is immediate and permanent.
- Last-used-at — visible in the settings UI so you can spot tokens that are no longer in use.
Creating an API Token
- Go to Settings → Credentials → API Tokens.
- Click Generate API Token.
- Pick the minimum set of scopes you need.
- Pick an expiry (keep the default unless you have a reason not to).
- Copy the token — you'll see the full value once.
curl https://checkupstream.com/api/v1/incidents \
-H "Authorization: Bearer cup_api_a1b2c3d4e5f6..."Pasting an SDK Key (cup_sdk_*) into an API Token field returns HTTP 403 with a helpful error message pointing you at the right credential type. We never silently accept the wrong kind.
AI Connections — OAuth for Claude, Cursor, and other MCP clients
Modern AI coding tools (Claude Desktop, Claude Code, Cursor, and other MCP clients) authorize against CheckUpstream over OAuth 2.1 + PKCE. You click Connect in your AI tool once, approve the CheckUpstream consent screen, and the tool gets a short-lived bearer token it refreshes automatically.
Why this is better than pasting a long-lived token into a config file:
- No long-lived secret sitting in
~/.config/claude/or.mcp.json - Tokens are short-lived and auto-rotate
- You can see every connected AI tool and revoke it in one click
- You can't accidentally commit a credential that no longer exists as a static string
You can see and manage connected tools in Settings → Credentials → AI Connections. See MCP Setup for the three-line config that wires up Claude Desktop, Claude Code, or Cursor.
Badges — no credential at all
Public dependency-health badges (/api/v1/badge/{org_slug}) are unauthenticated. This matches how shields.io, Codecov, Vercel, and every other README badge endpoint works: anyone with the slug can fetch an SVG of your current worst status.
Badge data is coarse (one of four statuses aggregated across all services) and doesn't expose service names, projects, or timelines. If your org has infrastructure sensitive enough that even the aggregate status shouldn't be public, turn badges off in Settings → Organization → Public Badges and the endpoint will return a neutral Private badge for everyone, including you.
Do not put a key in a badge URL. The badge endpoint never required authentication, and previous versions of these docs incorrectly suggested adding a ?key=... query parameter. README files are public — anything you put in a badge URL gets committed to every repo that embeds it. We've removed that example from the docs; if you see an old copy somewhere, delete it.
How the system prevents misuse
Naming is the first defense, but the code backs it up at every layer:
- Different prefixes. Every credential has a distinctive prefix (
cup_sdk_,cup_api_). Humans, regex-based secret scanners, and CI checks can all tell them apart at a glance. - Different config property names. The SDKs take
sdkKey, the REST API auth helper takes an API Token. Pasting the wrong value produces a type error in TypeScript or a runtime error with a clear "wrong credential type" message. - Endpoint-level enforcement. The REST and MCP endpoints reject anything that isn't an
cup_api_*token. The ingest endpoint rejects anything that isn't ancup_sdk_*key. The mismatch returns a 403 with an error message explaining which credential type to use. - Origin allowlist for SDK Keys. Browser/mobile SDK Keys can be restricted to specific origins, so a key leaked in a client bundle can't be reused from an attacker's origin.
- SDKs refuse to initialise with the wrong credential. Pass
cup_api_*tocheckupstream.init({ sdkKey: ... })and it fails at init time, before a single request leaves your process. - Expiring API Tokens. Default 90-day expiry on new API Tokens. Rotation has a 24-hour grace window so running services don't drop requests during a key swap.
- Revocation is instant. Revoking any credential invalidates the hash immediately — no propagation delay, no "up to an hour" caveats.
Common questions
I have an old CHECKUPSTREAM_API_KEY env var. Which credential type is it?
It's an API Token. The environment variable name still works (the SDK auto-init reads both CHECKUPSTREAM_SDK_KEY and CHECKUPSTREAM_API_KEY), but if you're setting up a new project, prefer CHECKUPSTREAM_SDK_KEY for SDK auto-init — the name makes it clear at a glance that the value is publishable.
Can I use one credential for both telemetry and REST? No. And that's on purpose. An SDK Key cannot call the REST API, and an API Token cannot call the ingest endpoint. This is how we guarantee that a leak in one surface doesn't compromise the other.
What happens if I commit an SDK Key to GitHub? Nothing bad — SDK Keys are publishable. The worst a leak can do is let someone submit telemetry events for your project (which is what the key is for). If you've set an origin allowlist, even that is limited to the allowed origins. That said, good hygiene is still to rotate leaked keys, just to keep your audit trail clean.
What happens if I commit an API Token to GitHub? Revoke it immediately in Settings → Credentials → API Tokens. API Tokens are secrets; a leak means someone can read (or mutate, depending on scope) your org's data. Generate a new one, grep your code for the old token, and double-check nothing else references it.
Can I have an API Token with no expiry?
Yes — pass expiresInDays: null when creating, but the UI will make you confirm deliberately. Non-expiring tokens are discouraged because they silently outlive the people who created them.