API surface
Programmatic access is locked down by default
An API key with a typo is one phishing email away from being a forgery factory. We design the API surface so that even if a key leaks, the blast radius is bounded by what you explicitly allowed it to do, from where, and how often.
Executive summary
Every API key is generated as a 32-byte cryptographic random, SHA-256 hashed before storage, and the raw value is shown to the user exactly once. We physically cannot recover or display a leaked key — only revoke it.
Webhook payloads are HMAC-SHA256 signed with a per-endpoint secret you control. Failed deliveries retry with exponential backoff and auto-disable after 15 consecutive failures, preventing a misconfigured endpoint from becoming an availability incident on the receiving end.
Our commitments
Five rules we won't break
Even at customer request — these are the lines that keep the API surface trustworthy.
Raw keys are never stored
We persist only the SHA-256 hash. Even with full database access, an attacker cannot replay a leaked key — they can only see it was used.
Minimum-permission tokens
API keys carry their own scopes, separate from user identity. A key for a worker can have permission to seal documents but not to invite users.
Webhook payloads are always signed
Every delivery includes an HMAC-SHA256 signature over the raw body, computed with a 32-byte secret unique to your endpoint.
Failures lock down, not loud
Repeated webhook delivery failures auto-disable the endpoint. We don't keep retrying into a black hole or spam the destination.
Rotation never breaks production
Atomic key rotation revokes the old key and provisions the new one in a single transaction. No window where both are absent.
Implementation — API keys
How API keys are hardened
Every value below is drawn from the source code, not aspiration.
Implementation — webhooks
How webhook delivery is hardened
Example signature verification (Python)
import hmac, hashlib
def verify(secret: bytes, raw_body: bytes, header: str) -> bool:
# header is the value of: x-credo-signature
if not header.startswith("sha256="):
return False
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header.removeprefix("sha256="))
Architecture
One audited API surface. Two transports. Every client.
Web sessions and host-app plugins (Adobe, Chrome) speak GraphQL v1. Native iOS, macOS, and Android clients speak gRPC v1 over HTTP/2. Both ride the same authorization model. Both pass through layered, code-enforced limits — the values below are read directly from the running app.
-
01
Unicode sanitizerStrips RTL-override and zero-width characters before parse — neutralizes invisible-Unicode and Trojan Source bidi attacks.
-
02
Automatic Persisted QueriesClients send a SHA-256 hash; raw query text is never re-shipped.
-
03
Query depth limit ≤ 10Hard ceiling enforced before resolution. Anything deeper rejects with a typed error.
-
04
Introspection guardSchema introspection disabled in production; configurable via a deploy-level flag.
-
05
Tiered complexity ceilingQueries: 500 default / 2000 enterprise. Mutations: 100 / 500. Subscriptions: 50 / 200. Per-field cost is uniformly 1; the resolver budget is bounded per operation.
-
06
Authenticate · Authorize middlewareToken resolved to a scoped user; the same 37-permission model that drives the dashboard gates every resolver.
-
07
Per-user rate limit10 req/min on expensive operations — the limit fires before the resolver fires.
-
08
Timeout · Telemetry · Structured logsEvery operation has a wall-clock ceiling, a telemetry event, and a structured log line — including the originating IP extracted from the rightmost non-private XFF entry.
-
01
HTTP/2 + TLS terminationCloudflare-issued cert on
grpc.engineeringid.com; plain HTTP refused at the edge. -
02
AuthInterceptor — explicit allowlistDefault for every RPC is "authenticated." Public methods must be enumerated by fully qualified path in
@public_rpcs— a privilege-by-default policy, not opt-out. -
03
Org-scoped authorizationScope-filtered queries enforce per-user/org isolation. A native client cannot escalate beyond what the user’s org grants.
-
04
Sensitive-data filter — enforced in testsPer project rule:
to_proto/1helpers must never emit signing certs, stamp image bytes, rejection reasons, verifier identity, or credential secrets. Test suite asserts this on every server module. -
05
Wire-compatibility lock
.protofiles are frozen at v1: no field removal, no renumber, no type change, no enum reorder, no RPC rename. Native clients deployed today will keep working in five years. -
06
Unary RPCs over HTTP/2Unary RPCs over HTTP/2 multiplexing; manifest events transport embedded JSON for forward-compat.
Credo.*
context modules.
One bug fix, one audit.
The full picture
What's built, what's being built, and what we chose not to build
Serious engineering means being explicit about the boundaries — including the work we deliberately rejected and why.
Live today
API keys with SHA-256 hashed storage
LiveRaw keys are never persisted; we can verify but not recover.
Per-key scopes and rate limits
Live3-tier scope (read < seal < full). Gateway limits: 100 req/min/IP on the API; 60 req/min/IP on the public verify endpoint.
HMAC-SHA256 webhook signatures
LivePer-endpoint 32-byte secret signs the raw JSON body. RFC 2104.
Auto-disable on 15 consecutive failures
LiveEndpoints flagged inactive automatically; admin must re-enable after fixing.
Atomic key rotation
LiveSingle Repo.transaction wraps revoke + create. No window of inconsistency.
Building now
Per-key IP CIDR allow-lists
Building nowRestrict an API key to specific source IPs at the gateway. Already supported in the schema; admin UI in progress.
Target: this quarter.
GitHub secret scanning push protection
Building nowRegister the credo_ prefix with GitHub so leaked keys are blocked at commit time and revoked automatically on push.
Target: registration submitted; awaiting onboarding.
Webhook destination domain allow-list
Building nowOrg-level setting that restricts webhook endpoints to a strict list of hostnames.
Roadmap
Mutual TLS for webhook delivery
RoadmapOptional client-cert authentication on outbound webhook delivery. Adds transport-level identity binding on top of HMAC.
Driven by Enterprise customer requests; tracking real demand.
Signed receipts on webhook acknowledgement
RoadmapRecipient signs the response back to us so we can prove the destination received and processed the event.
Per-key request signing (HMAC over the request)
RoadmapOptional second factor that signs the full request body alongside the bearer token, for extra-paranoid integrations.
Considered & rejected
Blockchain anchoring of API events
Considered & rejectedA chained SHA-256 audit log gives the same tamper-evidence at zero cost per write.
Why we rejected it: blockchain adds latency, unbounded cost, and probabilistic finality without solving any concrete trust problem we have. Cryptographic chaining is the published-standard answer to 'prove this log is unaltered.'
JWT-based API access tokens
Considered & rejectedStatefully-issued opaque keys give us instant revocation; JWTs would require a second blocklist anyway.
Why we rejected it: the only argument for JWTs over opaque tokens is 'no DB lookup,' but the moment you need revocation you're checking a blocklist on every request — at which point opaque tokens with constant-time hash compare are simpler and faster.
OAuth 2.0 device flow for service accounts
Considered & rejectedA long-lived API key with explicit scope is the right primitive for unattended workloads.
Why we rejected it: device flow assumes a human at a phone confirming a code. Server-to-server integrations don't have one. Forcing OAuth where it doesn't fit creates worse security than a well-designed API key.
Storing the last 4 characters of API keys
Considered & rejectedThe prefix tells you which key was used; the suffix tells an attacker how close they are.
Why we rejected it: any displayable 'fingerprint' beyond the prefix lets an attacker confirm a partial-leak guess. We show prefix-only, period.
Compliance mappings
Controls this surface satisfies
For your auditor's worksheet — concrete control IDs, not marketing claims.
Logical Access — Authentication
Hashed credential storage, per-key scopes, constant-time auth
Logical Access — Network Segmentation
HTTPS-only webhook delivery; signed payload integrity
System Operations — Anomaly Detection
Per-key usage tracking, automatic disable on repeated failures
Secure log-on procedures
Token-based auth with revocation, expiration, scope
Cryptographic controls
HMAC-SHA256 (RFC 2104) for all webhook signing
Event logging
Every API request logged with key prefix, IP, endpoint, status
For compliance teams
Questions you don't need to call to ask
What happens to API keys when an employee leaves?
Can I restrict an API key to specific source IPs?
How are leaked secrets detected?
Do you support mTLS for webhook delivery?
What's logged for each API call?
Build with confidence on a hardened surface
Read the API reference, or talk to our security team about your specific control requirements.