Developer portal · Phase 4K
Build on the Crest standard
Two surfaces: the keyless Padel Decoder (open data — venues, tournaments, coaches) and the keyed Crest API for player ratings, history, webhooks. This page covers the keyed surface. The same buyer licence applies whenever you consume Crest data downstream.
Quickstart
Key to first call in four steps
Sign up + create a key in /account/api-keys. Keys start in Free tier; upgrade by emailing developers@trustpadel.com.
Copy the plaintext key shown once. Format is crst_xxxxxxxx.<secret> — the prefix is visible in logs; the secret half is bcrypt-hashed in storage.
Authenticate with Authorization: Bearer crst_xxxxxxxx.<secret>.
Test against the sandbox players before pointing at production handles. The minor + private fixtures specifically verify your code handles the 404-indistinguishable invariant.
Code samples
Three minimal clients
Each hits the public Crest endpoint and handles the 404-indistinguishable invariant correctly — treat “not found” as “no public Crest”, never as “account doesn't exist”.
# curl
KEY="crst_xxxxxxxx.your_secret"
curl -sS \
-H "Authorization: Bearer $KEY" \
https://trustpadel.com/api/v1/players/sandbox-gold/rating
# 200 → { "handle": "sandbox-gold", "crest": { ... } }
# 404 → opaque; could be missing, minor, or privateWebhook signature verification
// node
import crypto from 'node:crypto'
export function verifyCrestWebhook(req, signingSecret) {
const sig = req.headers['x-crest-signature']
if (!sig) return false
const [ts, hex] = sig.split(',')
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false // ±5min
const expected = crypto
.createHmac('sha256', signingSecret)
.update(`${ts}.${req.rawBody}`)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(hex, 'hex'))
}Endpoints
The keyed surface
All keyed routes count toward your tier rate limit. Keyless /decoder routes don't. Deprecation + Sunset headers signal scheduled changes per RFC 8594 with a 6-month minimum window. Machine-readable spec (OpenAPI 3.1) available. Building an app that signs users in? Register an OAuth app for Sign in with TrustPadel.
Rate limits + tiers
Pick the volume you need
Tier upgrades route through billing — email developers@trustpadel.com with your prefix + intended use.
Scopes
Scope a key tightly
Keys carry a scope set. Write scopes (results:write, webhooks:manage) require Developer tier or above. A credential lost from a read-only integration can't mutate data.
Webhooks
At-least-once delivery, signed
Configure endpoints per key in /account/api-keys. Endpoints must be HTTPS. Delivery is at-least-once with exponential backoff. After repeated failures the endpoint pauses automatically; you resume it from the same page.
Signing
# Pseudocode
sig_header = req.headers['X-Crest-Signature']
ts, sig = sig_header.split(',')
if abs(now() - int(ts)) > 300: reject
expected = hmac_sha256(signing_secret, f"{ts}.{raw_body}")
if not const_time_eq(expected, sig): rejectEach request carries an X-Crest-Signature header containing an HMAC-SHA256 over <timestamp>.<raw-body> keyed by the signing secret returned at endpoint creation. Reject requests more than ±5 minutes from your clock to prevent replays. Always verify the signature before processing the payload.
Events
crest.updated
A player's Crest number / tier changed after a confirmed match.
result.confirmed
A match transitioned to confirmed (all parties agreed, above integrity gate).
result.disputed
A confirmed match entered dispute and is quarantined from feeds.
player.handle_changed
A player renamed; their old handle 301s in handle_history.
identity.merged
Two records were resolved into one through the identity-resolution queue.
integrity.flag_raised
A Layer-2 or Layer-3 integrity signal fired against a player or match.
subscription.entitlement_changed
A dataset entitlement was issued / renewed / revoked.
Sandbox players
Six fixtures covering the locked invariants
Hit these against your integration before pointing at production. The sandbox-minor and sandbox-private fixtures specifically verify the 404 invariant — your code must render the same UI for both as for a truly absent player.
sandbox-gold200 OK
Adult, Gold tier, ~1750 Crest, public.
sandbox-diamond200 OK
Adult, Diamond tier, ~2400 Crest, public + Trust+ active.
sandbox-provisional200 OK
Provisional flag true, confidence band wide.
sandbox-minor404
Minor account. NEVER 200. Use this to verify your integration honours the indistinguishable-404 invariant.
sandbox-private404
Adult with rating_public=false. Same 404 shape as the minor case (the absence-is-indistinguishable rule).
sandbox-disputed200 OK
But the most recent match is disputed; the matching result.disputed webhook fires on test events.
Locked invariants
Non-negotiable across every endpoint, every tier, every release
If your integration depends on the inverse of any of these, please choose a different integration.
Minor + private return indistinguishable 404.
No status code, no field, no metadata distinguishes a missing player from a minor or private one. Your UI must not infer existence from latency, header, or response shape.
Article-9 tags never appear in feeds.
Identity, condition, and preference tags are protected and absent from every keyed response. If a field that looks Art-9 ever appears, please report it immediately.
Provisional Crests are clearly marked.
The provisional boolean + confidence_band let you avoid presenting unstable ratings as firm.
Integrity-gated commercial feeds.
Records below the provenance or integrity threshold are absent from the licensed feeds, not flagged in them. Re-pulls after disputes resolve will surface the corrected record.
Questions?