Skip to content

Authorization (OAuth 2.1)

VibeSell runs its own OAuth 2.1 authorization server, built for MCP clients: public clients with PKCE, dynamic client registration, and a consent page where the account owner signs in. You don’t need a client id or secret: a compliant client discovers and registers everything itself.

  1. Challenge. An unauthenticated POST /v1/mcp answers 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/v1/mcp", scope="mcp mcp:act".

  2. Discovery. The client reads the protected resource metadata (its authorization_servers), then /.well-known/oauth-authorization-server.

  3. Registration. The client POSTs its metadata to /v1/oauth/register and gets a client_id.

  4. Authorization. The client sends the browser to /v1/oauth/authorize with response_type=code, a PKCE code_challenge (S256), its redirect_uri, a state and the resource. VibeSell redirects to its consent page, where the user signs in and clicks Allow.

  5. Code. VibeSell redirects back to the redirect_uri with code, state and iss.

  6. Token. The client exchanges the code and its code_verifier at /v1/oauth/token for an access token and a refresh token.

GET /.well-known/oauth-authorization-server
{
"issuer": "https://api.vibesell.it",
"authorization_endpoint": "https://api.vibesell.it/v1/oauth/authorize",
"token_endpoint": "https://api.vibesell.it/v1/oauth/token",
"registration_endpoint": "https://api.vibesell.it/v1/oauth/register",
"revocation_endpoint": "https://api.vibesell.it/v1/oauth/revoke",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"code_challenge_methods_supported": [
"S256"
],
"token_endpoint_auth_methods_supported": [
"none"
],
"revocation_endpoint_auth_methods_supported": [
"none"
],
"scopes_supported": [
"mcp",
"mcp:act"
],
"authorization_response_iss_parameter_supported": true
}
GET /.well-known/oauth-protected-resource/v1/mcp
{
"resource": "https://api.vibesell.it/v1/mcp",
"authorization_servers": [
"https://api.vibesell.it"
],
"scopes_supported": [
"mcp",
"mcp:act"
],
"bearer_methods_supported": [
"header"
],
"resource_name": "VibeSell"
}

POST /v1/oauth/register takes JSON client metadata (RFC 7591):

  • redirect_uris — 1 to 10 addresses, each either an https:// URL or a loopback http:// URL on localhost, 127.0.0.1 or [::1], without a fragment.
  • client_name — shown on the consent page (up to 200 characters). Without one, the host of the first redirect URI is shown.

Clients are public: token_endpoint_auth_method is none, and no secret is issued.

Parameter Rule
client_id A registered client. An unknown one is refused without redirecting.
redirect_uri One the client registered. An unknown one is refused without redirecting.
response_type code only.
code_challenge / code_challenge_method PKCE is mandatory, S256 only.
state Echoed back; up to 2,000 characters.
resource Optional; if given, must be https://api.vibesell.it/v1/mcp (a trailing slash is tolerated).
scope See below.

The consent request lives 10 minutes; the authorization code 2 minutes. The redirect back carries iss=https://api.vibesell.it (RFC 9207). If the user clicks Deny, the client gets error=access_denied.

Scope Grants
mcp Reading. Always granted.
mcp:act Acting in the workspace: saving work, preparing approval cards.
  • A request that names none of these scopes gets both.
  • A request that names mcp:act gets mcp mcp:act; one that names only mcp gets a read-only connection.
  • Other scopes (offline_access…) are ignored: they neither open nor block anything.

The consent page lists what the granted scopes allow before the user says yes.

Lifetime
Access token 60 minutes (expires_in: 3600)
Refresh token 30 days

Tokens are opaque strings, stored by VibeSell only as hashes. Their audience is the MCP resource.

Refresh tokens rotate. Each grant_type=refresh_token call revokes the refresh token it uses and returns a new pair. Presenting an already-rotated refresh token is treated as theft and revokes the whole connection — except within 60 seconds of its rotation, where it simply fails with invalid_grant (clients such as Claude may refresh twice at once).

POST /v1/oauth/revoke (form-encoded token, RFC 7009) revokes a token and answers 200. Users revoke a whole connection from Settings → Connected apps in VibeSell.

OAuth endpoints answer in the RFC 6749 / 7591 format:

{ "error": "invalid_grant", "error_description": "the refresh_token was already used" }
error When
invalid_request A missing or malformed parameter — PKCE absent, state too long, unregistered redirect_uri.
invalid_client Unknown client_id (HTTP 401 on the token endpoint, which makes clients register again).
invalid_grant Expired, already used or unknown code or refresh token; PKCE verifier mismatch.
unsupported_grant_type A grant other than authorization_code or refresh_token.
unsupported_response_type A response_type other than code.
invalid_target A resource other than the MCP’s URL.
invalid_redirect_uri Registration with no redirect URI, too many, or one that is neither https:// nor loopback.
access_denied The user clicked Deny.