Skip to content

Guides

Connect an app (OAuth 2.1)

When your app acts on behalf of a SkedCast user, use OAuth 2.1 — the Authorization Code flow with PKCE (S256, mandatory). Clients register dynamically (no manual app setup) and discover every endpoint from the metadata document. This is the end-to-end flow with copy-paste examples. (For your own server-side scripts, an API key is simpler — see Authentication.)

Browse docs

8 min read

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.

bash
curl https://api.skedcast.com/.well-known/oauth-authorization-server

2. 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).

bash
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"
  }'
redirect_uris must be https (or http loopback for native / CLI clients).
json
{
  "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-…"
}
Response — keep client_id; the rest is echoed back.

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.

ts
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");
PKCE (Node) — verifier stays server-side, challenge goes in the URL.

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).

http
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/v1
The authorize URL (line-wrapped for readability).

5. 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).

bash
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
json
{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "posts.read posts.compose",
  "refresh_token": "eyJ…"
}
Token response.

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.

bash
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.

bash
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.

bash
curl -X POST https://api.skedcast.com/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d token=eyJ…
oauthpkceauthenticationdcr

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).

Be first in line when SkedCast opens

Join the waitlist — agencies on it get early access and launch-day onboarding.