REST & OAuth Endpoints
Complete reference with curl examples. All endpoints are on cpod-backend at https://api.yourdomain.com. The Control Plane (:8080) is internal — cpod-backend proxies to it; you never call it directly.
For the gRPC surface (sidecar-internal) see the SDK Reference.
OAuth — Token Issuance
POST /v1/oauth/token — client_credentials
curl -X POST https://api.yourdomain.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=app-myservice&client_secret=cs_xxxx&scope=jobs.read+files.write"{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "jobs.read files.write"
}POST /v1/oauth/token — authorization_code
curl -X POST https://api.yourdomain.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&client_id=app-myapp&code=AUTH_CODE&redirect_uri=https://myapp.example.com/callback&code_verifier=VERIFIER"{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"scope": "jobs.read files.read"
}POST /v1/oauth/token — refresh_token
curl -X POST https://api.yourdomain.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&client_id=app-myapp&refresh_token=rt_xxxx"Returns a new access_token and a new refresh_token. The old refresh token is immediately revoked.
OAuth — Authorization Code + PKCE
GET /oauth/authorize — Start PKCE Flow
Redirect the user’s browser to this URL. cpod-backend renders the consent screen.
GET https://api.yourdomain.com/oauth/authorize
?client_id=app-myapp
&response_type=code
&redirect_uri=https://myapp.example.com/callback
&scope=jobs.read+files.read
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=xyzABC123After consent, cpod-backend redirects to:
https://myapp.example.com/callback?code=AUTH_CODE&state=xyzABC123Exchange the code server-side immediately — codes expire in 60 seconds and are single-use. Never exchange auth codes in browser JavaScript.
OAuth — Token Inspection & Revocation
POST /v1/oauth/introspect — Inspect Token (RFC 7662)
curl -X POST https://api.yourdomain.com/v1/oauth/introspect \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=ACCESS_TOKEN&client_id=app-myservice&client_secret=cs_xxxx"Active token:
{
"active": true,
"client_id": "app-myservice",
"scope": "jobs.read files.write",
"sub": "app-myservice",
"iss": "https://api.yourdomain.com",
"aud": "api.yourdomain.com",
"iat": 1748820000,
"exp": 1748823600,
"jti": "jti_a1b2c3d4"
}Expired or revoked:
{ "active": false }POST /v1/oauth/revoke — Revoke Token (RFC 7009)
curl -X POST https://api.yourdomain.com/v1/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=ACCESS_TOKEN&client_id=app-myservice&client_secret=cs_xxxx"Returns 200 OK for both valid and already-expired tokens (per RFC 7009). JTI is added to the sidecar deny-list and propagated within 30 s.
App Management
All app management endpoints require an admin token: Authorization: Bearer $CPOD_ADMIN_TOKEN
POST /v1/oauth/apps — Register App
curl -X POST https://api.yourdomain.com/v1/oauth/apps \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CPOD_ADMIN_TOKEN" \
-d '{
"client_id": "app-myservice",
"name": "My Backend Service",
"declared_scopes": ["jobs.read", "jobs.write", "files.read"],
"app_type": "service"
}'{
"client_id": "app-myservice",
"client_secret": "cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "My Backend Service",
"declared_scopes": ["jobs.read", "jobs.write", "files.read"],
"app_type": "service",
"created_at": "2026-05-21T00:00:00Z"
}client_secret is returned once only — stored argon2-hashed. Store it in your secrets manager immediately.
app_type values:
| Value | Grant types allowed | Use case |
|---|---|---|
service | client_credentials | Backend services, workers, CI |
web | authorization_code + refresh_token | Server-rendered web apps |
spa | authorization_code + PKCE (no secret) | Single-page apps |
cli | authorization_code + PKCE | CLI tools |
GET /v1/oauth/apps — List Apps
curl https://api.yourdomain.com/v1/oauth/apps \
-H "Authorization: Bearer $CPOD_ADMIN_TOKEN"DELETE /v1/oauth/apps/:id — Delete App
curl -X DELETE https://api.yourdomain.com/v1/oauth/apps/app-myservice \
-H "Authorization: Bearer $CPOD_ADMIN_TOKEN"Returns 204 No Content. All active tokens for the app are revoked immediately.
POST /v1/oauth/apps/:id/rotate-secret — Rotate Secret
curl -X POST https://api.yourdomain.com/v1/oauth/apps/app-myservice/rotate-secret \
-H "Authorization: Bearer $CPOD_ADMIN_TOKEN"{
"client_id": "app-myservice",
"client_secret": "cs_yyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"rotated_at": "2026-05-21T12:00:00Z"
}Old secret is invalidated immediately. For zero-downtime rotation: deploy new secret to your app first, then call rotate.
JWKS
GET /v1/jwks — Public Key Set
No authentication required. Returns the RS256 public keys so external services can verify CoreSDK-issued JWTs without calling the sidecar.
curl https://api.yourdomain.com/v1/jwks{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "key-2026-05-21",
"n": "...",
"e": "AQAB"
}
]
}Health
curl https://api.yourdomain.com/health
# {"status":"ok","sidecar":"connected","version":"0.9.0"}