Skip to Content
API ReferenceOAuth ServerOverview

OAuth 2.1 API

VDClip runs an OAuth 2.1 Authorization Server (issuer https://api.vdclip.com) for MCP servers, AI agents, and third-party integrations that act on behalf of a user. Instead of a static API key, these clients get a short-lived access token through the authorization-code flow with PKCE and call the API with Authorization: Bearer <JWT>.

When to use OAuth

  • OAuth 2.1 Bearer token. When a client acts on behalf of a user: an MCP server, AI agent, or third-party integration. The user signs in and consents, the client receives a JWT, and every request carries Authorization: Bearer <JWT>.
  • Static Authorization Bearer. For your own server-to-server calls that do not represent a user. See Authentication.

How the flow works

  1. Discover server metadata at /.well-known/oauth-authorization-server and signing keys at /.well-known/jwks.json.
  2. Register a client with Dynamic Client Registration (POST /oauth2/application), declaring the scopes it needs.
  3. Authorize: send the user to the dashboard authorization URL from discovery with a PKCE challenge. The dashboard owns consent UI and callback handling.
  4. Exchange the authorization code for tokens at POST /oauth2/token.
  5. Call /v1/* with Authorization: Bearer <JWT>, and refresh or revoke tokens as needed.

Complete public-client example

Example below uses a public PKCE client. Do not add a client secret. Keep CLIENT_ID, REGISTRATION_ACCESS_TOKEN, ACCESS_TOKEN, and REFRESH_TOKEN in a secret store or process memory.

1. Discover endpoints and supported scopes

ISSUER="https://api.vdclip.com" curl -sS "$ISSUER/.well-known/oauth-authorization-server" | jq .

Use authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint, and scopes_supported from response. Do not hard-code environment-specific hosts when discovery is available.

2. Register client

REGISTRATION=$(curl -sS -X POST "$ISSUER/oauth2/application" \ -H "Content-Type: application/json" \ -d '{ "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "redirect_uris": ["https://app.example.com/oauth/callback"], "client_name": "Example App", "scope": "vdclip:projects:read vdclip:results:read" }') CLIENT_ID=$(jq -r '.client_id' <<<"$REGISTRATION") REGISTRATION_ACCESS_TOKEN=$(jq -r '.registration_access_token' <<<"$REGISTRATION")

Registration response contains registration_access_token once. Store it. It is different from user access tokens and is required to read registration later.

3. Create PKCE values and open authorization URL

REDIRECT_URI="https://app.example.com/oauth/callback" STATE=$(openssl rand -hex 16) CODE_VERIFIER=$(openssl rand -base64 64 | tr -dc 'A-Za-z0-9-._~' | cut -c1-64) CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \ | openssl dgst -sha256 -binary \ | openssl base64 -A \ | tr '+/' '-_' | tr -d '=') AUTHORIZATION_ENDPOINT=$(curl -sS "$ISSUER/.well-known/oauth-authorization-server" \ | jq -r '.authorization_endpoint') echo "$AUTHORIZATION_ENDPOINT?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=vdclip%3Aprojects%3Aread%20vdclip%3Aresults%3Aread&state=$STATE&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256"

Open printed URL in browser. Verify returned state equals original STATE. Callback receives code. Never accept callback without matching state.

4. Exchange code for tokens

Public clients send client_id in form body. HTTP Basic is also accepted, but must identify same client. Send original redirect URI and verifier.

TOKEN_RESPONSE=$(curl -sS -X POST "$ISSUER/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode "code=$AUTHORIZATION_CODE" \ --data-urlencode "redirect_uri=$REDIRECT_URI" \ --data-urlencode "code_verifier=$CODE_VERIFIER") ACCESS_TOKEN=$(jq -r '.access_token' <<<"$TOKEN_RESPONSE") REFRESH_TOKEN=$(jq -r '.refresh_token' <<<"$TOKEN_RESPONSE")

5. Call API, refresh, and revoke

curl -sS "$ISSUER/v1/projects?limit=20" \ -H "Authorization: Bearer $ACCESS_TOKEN" curl -sS -X POST "$ISSUER/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode "refresh_token=$REFRESH_TOKEN" curl -sS -X POST "$ISSUER/oauth2/revoke" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "token=$REFRESH_TOKEN" \ --data-urlencode "token_type_hint=refresh_token" \ --data-urlencode "client_id=$CLIENT_ID"

Token and revoke endpoints use RFC-shaped responses. They do not use /v1 errors[] response contract.

Optional and conditional fields

Verified against current API schemas and handlers:

RequestRequiredOptional or conditional behavior
DCR POST /oauth2/applicationclient_name, redirect_uris keystoken_endpoint_auth_method defaults to none; grant_types defaults to authorization_code; response_types defaults to code; metadata URLs, scope, and allowed_resources can be omitted. Empty redirect_uris works only for refresh-token-only registration.
Authorization-code token requestgrant_type, client_id or HTTP Basic, code, redirect_uricode_verifier is conditional on PKCE. Public clients should always send it. scope and resource are not used for this grant.
Refresh-token requestgrant_type, client_id or HTTP Basic, refresh_tokenscope and resource are optional. code, redirect_uri, and code_verifier are not used.
Revoke requesttokentoken_type_hint and client_id are optional. Response is always HTTP 200 with an empty JSON object.

Optional in schema does not mean valid in every flow. Handler validation applies after form parsing.

Conventions

These endpoints live at /oauth2/* and /.well-known/* and use standard RFC-shaped bodies, not the /v1 envelope. Handle their responses and errors separately. Discovery and JWKS are public; authorize, consent, and token drive the interactive login.

Once a client holds a Bearer token, it calls /v1/* exactly as documented elsewhere. Scopes follow the vdclip:<resource>:<action> convention, with the same plan gating: render:execute and social:publish depend on the user’s plan. The full scope catalog is in MCP Servers. See pricing  for plan details.

Standards and RFCs

VDClip implements OAuth 2.1. Each endpoint follows the relevant standard:

RFCSpecificationUsed by
RFC 6749 OAuth 2.0 Authorization FrameworkAuthorization and token endpoints
RFC 6750 Bearer Token UsageAuthorization: Bearer <JWT> on /v1 calls
RFC 7636 Proof Key for Code Exchange (PKCE)Authorization endpoint
RFC 8414 Authorization Server MetadataDiscovery (/.well-known/oauth-authorization-server)
RFC 7517 JSON Web Key Set (JWKS)/.well-known/jwks.json
RFC 7591 Dynamic Client RegistrationPOST /oauth2/application
RFC 7592 Client Registration ManagementGET /oauth2/application/{client_id}
RFC 9068 JWT Profile for Access TokensAccess tokens (at+jwt, ES256)
RFC 7009 Token RevocationPOST /oauth2/revoke

Endpoints

🧭

Server metadata

GET /.well-known/oauth-authorization-server

ViewDiscover authorization server endpoints and capabilities
🔑

Public keys

GET /.well-known/jwks.json

ViewFetch the public keys used to verify access tokens
📝

Register a client

POST /oauth2/application

ViewRegister a new OAuth client and receive its credentials
📄

Read client registration

GET /oauth2/application/{client_id}

ViewRead the current registration for an existing client
🎟️

Get a token

POST /oauth2/token

ViewExchange an authorization code or refresh an access token
🚫

Revoke a token

POST /oauth2/revoke

ViewRevoke an access or refresh token
Last updated on