Browse docs
The flow at a glance#
- Discover the authorization server from the well-known metadata.
- Register your client once (Dynamic Client Registration) to get a client_id.
- Generate a PKCE verifier + challenge per authorization.
- Redirect the user to /oauth/authorize; they sign in and consent.
- Exchange the returned code at /oauth/token for an access + refresh token.
- Call the API; refresh when the access token expires; revoke on logout.
1. Discover the authorization server#
Everything you need is in the RFC 8414 metadata document — endpoints, supported scopes, and the PKCE method. Read endpoints from it rather than hard-coding paths.
curl https://api.skedcast.com/.well-known/oauth-authorization-server2. Register your client (DCR)#
Register once (RFC 7591) to get a client_id. SkedCast issues PUBLIC clients only (PKCE, no client secret). Keep the registration_access_token if you want to update or delete the registration later (RFC 7592).
curl -X POST https://api.skedcast.com/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My App",
"redirect_uris": ["https://myapp.com/oauth/callback"],
"scope": "posts.read posts.compose"
}'{
"client_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"client_id_issued_at": 1751299200,
"redirect_uris": ["https://myapp.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"scope": "posts.read posts.compose",
"registration_access_token": "reg_…",
"registration_client_uri": "https://api.skedcast.com/oauth/register/f47ac10b-…"
}3. Generate a PKCE pair#
Per authorization, create a high-entropy code_verifier and its S256 code_challenge (RFC 7636). Keep the verifier private; put the challenge in the authorize URL.
import { randomBytes, createHash } from "node:crypto";
// code_verifier: 43-128 unreserved chars (RFC 7636 §4.1)
const codeVerifier = randomBytes(32).toString("base64url");
// code_challenge = base64url(SHA-256(verifier)) (RFC 7636 §4.2)
const codeChallenge = createHash("sha256")
.update(codeVerifier)
.digest("base64url");4. Send the user to authorize#
Redirect the user's browser to /oauth/authorize with your client_id, scopes, a random state (CSRF), and the PKCE challenge. They sign in and consent; SkedCast redirects back to your redirect_uri with ?code=…&state=…. Verify state matches what you sent.
Include resource to pick which SkedCast resource the token is for — https://api.skedcast.com/v1 for the REST API (or the MCP server for MCP). Tokens are audience-bound (RFC 8707) and the audience is fixed here, at the authorize step; send the same value at the token step too (it is validated when present, and a mismatch is rejected).
GET https://api.skedcast.com/oauth/authorize
?response_type=code
&client_id=f47ac10b-…
&redirect_uri=https://myapp.com/oauth/callback
&scope=posts.read%20posts.compose
&state=<random-csrf-token>
&code_challenge=<codeChallenge>
&code_challenge_method=S256
&resource=https://api.skedcast.com/v15. Exchange the code for tokens#
POST the code back with your code_verifier. SkedCast verifies the PKCE challenge and returns an access + refresh token. Send the same resource you used at the authorize step — the token endpoint validates it when present and rejects a mismatch with invalid_target (the audience itself was fixed at authorize time).
curl -X POST https://api.skedcast.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=<authorization_code> \
-d redirect_uri=https://myapp.com/oauth/callback \
-d client_id=f47ac10b-… \
-d code_verifier=<codeVerifier> \
-d resource=https://api.skedcast.com/v1{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 900,
"scope": "posts.read posts.compose",
"refresh_token": "eyJ…"
}6. Call the API#
Send the access token as a Bearer credential — exactly like an API key. Its power is still scopes ∩ the user's role.
The access token must have been issued for the REST API resource (resource=https://api.skedcast.com/v1 in step 4) — an MCP-only token is rejected here.
curl https://api.skedcast.com/v1/agency \
-H "Authorization: Bearer eyJ…"7. Refresh when it expires#
Access tokens last 15 minutes. Exchange your refresh token for a new pair before, or when a call returns 401. The refreshed token keeps the same resource as the original and is re-checked against your current role — scopes may narrow if the member was demoted.
curl -X POST https://api.skedcast.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=refresh_token \
-d refresh_token=eyJ… \
-d client_id=f47ac10b-…8. Revoke on logout#
When the user disconnects, revoke the refresh token (RFC 7009) — this kills the whole rotation family. The endpoint always returns 200, even for an unknown token, so it never leaks token state.
curl -X POST https://api.skedcast.com/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d token=eyJ…FAQ
- Do I need a client secret?
- No. SkedCast registers public clients only and requires PKCE (S256), so there is no client secret to store — the code_verifier proves possession at the token step.
- What is the resource parameter for?
- OAuth access tokens are audience-bound (RFC 8707). The resource parameter picks which resource the token is for, and the audience is fixed at the authorize step: to call the REST API you MUST pass resource=https://api.skedcast.com/v1 on /oauth/authorize — omitting it there yields an MCP-only token that 401s on /v1. Repeat the same value on /oauth/token (validated when present; a mismatch is rejected with invalid_target).