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.
The flow
Section titled “The flow”-
Challenge. An unauthenticated
POST /v1/mcpanswers401withWWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/v1/mcp", scope="mcp mcp:act". -
Discovery. The client reads the protected resource metadata (its
authorization_servers), then/.well-known/oauth-authorization-server. -
Registration. The client
POSTs its metadata to/v1/oauth/registerand gets aclient_id. -
Authorization. The client sends the browser to
/v1/oauth/authorizewithresponse_type=code, a PKCEcode_challenge(S256), itsredirect_uri, astateand theresource. VibeSell redirects to its consent page, where the user signs in and clicks Allow. -
Code. VibeSell redirects back to the
redirect_uriwithcode,stateandiss. -
Token. The client exchanges the code and its
code_verifierat/v1/oauth/tokenfor an access token and a refresh token.
Metadata
Section titled “Metadata”{ "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}{ "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"}Client registration
Section titled “Client registration”POST /v1/oauth/register takes JSON client metadata (RFC 7591):
redirect_uris— 1 to 10 addresses, each either anhttps://URL or a loopbackhttp://URL onlocalhost,127.0.0.1or[::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.
Authorization request
Section titled “Authorization request”| 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.
Scopes
Section titled “Scopes”| 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:actgetsmcp mcp:act; one that names onlymcpgets 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.
Tokens
Section titled “Tokens”| 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).
Revocation
Section titled “Revocation”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.
Errors
Section titled “Errors”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. |