Spalce supports three authentication mechanisms. Pick the right one for your use case: API keys for server-to-server traffic, OAuth 2.0 for third-party apps acting on behalf of a user, and webhook signatures for verifying incoming events. All three are designed to fail safely — a missing or malformed credential always returns 401, never silently downgrades scope.
API keys
API keys are the simplest option and the right choice for any workload running on infrastructure you control. They are scoped to a single environment, can be restricted to a set of resources or actions, and are revocable from the dashboard. Spalce shows a key value exactly once at creation — after that, only the prefix and a hashed identifier are stored.
curl https://api.spalce.dev/v1/customers \
-H "Authorization: Bearer sk_live_8vN3..."Never commit a key to source control. Our scanners revoke any key that lands on a public GitHub repository within ninety seconds.
Restricting key scope
Restricted keys let you mint a credential that can only read a single resource type, only write to a single environment, or only operate within a specific IP range. We recommend a restricted key per service rather than a single root key shared across your fleet — it makes revocation surgical when something leaks.
{
"name": "orders-service-prod",
"scopes": ["orders:read", "orders:write", "customers:read"],
"ip_allowlist": ["203.0.113.0/24"],
"expires_at": "2027-01-01T00:00:00Z"
}OAuth 2.0
If you are building a product that connects to your customers' Spalce workspaces, use the OAuth 2.0 authorization code flow with PKCE. Register your application in the dashboard to receive a client_id and client_secret, then redirect users to our authorization endpoint with the scopes you need. We support standard refresh tokens with rotation enabled by default.
https://auth.spalce.dev/oauth/authorize
?response_type=code
&client_id=acme_app_123
&redirect_uri=https://acme.com/callback
&scope=read_customers+write_orders
&state=random_csrf_token
&code_challenge=...
&code_challenge_method=S256Webhook signatures
Every webhook payload is signed with HMAC-SHA256. The signature is delivered in the Spalce-Signature header along with a timestamp. Verify both — reject requests older than five minutes to defeat replay attacks, and use a constant-time comparison to defeat timing attacks.
import crypto from "node:crypto";
export function verify(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Date.now() / 1000 - Number(parts.t);
if (age > 300) throw new Error("Signature too old");
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Rotate webhook signing secrets quarterly. The dashboard supports dual-secret windows so you can roll without downtime.
Rate limits and 401 vs 403
A 401 means we could not authenticate the request at all — the key was missing, malformed, or revoked. A 403 means the credential is valid but lacks the required scope. The two are easy to confuse during incident response, so we keep them strictly separate and never collapse one into the other.
- 401 Unauthorized — credential missing, malformed, or revoked.
- 403 Forbidden — credential valid but the scope is insufficient.
- 429 Too Many Requests — credential valid and authorized, but rate limited.
Was this article helpful?
