Vai al contenuto principale

MCP OAuth (connector authorization)

The VIVIN API is an OAuth 2.1 authorization server for MCP clients: a landlord signs in with VIVIN and approves the connection, and the client gets a short-lived token for the VIVIN MCP server.

Before you start​

ItemValue
Who it is forDevelopers who build or debug an MCP client. To connect Claude or ChatGPT, follow Connect Claude, ChatGPT or other AI tools: the client runs this flow for you
Base URLhttps://api.vivin.app for every endpoint on this page
MCP server URLShown in Settings → AI / MCP → MCP connection. Users who do not manage AI settings see it in the Connect your AI client card on the same tab
AuthenticationThe /.well-known/* and /oauth/* routes are public: no Authorization header
Not the same asPartner tokens (Authentication) and the app's own sign-in (Management session). A client that cannot do OAuth sends an API key as a Bearer token instead; an Admin or Super Admin generates it in Settings → AI / MCP → API keys

How the flow works​

  1. The client calls the MCP server URL without a token. The MCP server answers 401 with WWW-Authenticate: Bearer resource_metadata="<MCP server>/.well-known/oauth-protected-resource".
  2. That protected-resource metadata names the VIVIN API as the authorization server. The client reads discovery there.
  3. The client registers and gets a client_id.
  4. The client opens GET /oauth/authorize in the landlord's browser with a PKCE challenge.
  5. The landlord clicks Authorize on the consent page. The API redirects to the client's redirect_uri with a code.
  6. The client exchanges the code at POST /oauth/token for an access token and a refresh token.
  7. The client calls the MCP server with Authorization: Bearer <access_token> and refreshes the token before it expires.

Endpoints​

MethodPathWhat it doesPer IP address and minute
GET/.well-known/oauth-authorization-serverAuthorization server metadata (RFC 8414)120
GET/.well-known/openid-configurationThe same metadata120
POST/oauth/registerDynamic client registration (RFC 7591)20
GET/oauth/authorizeThe consent page60
POST/oauth/authorizeThe landlord's decision on the consent page10
POST/oauth/tokenAuthorization-code and refresh-token grants60

Discovery​

GET /.well-known/oauth-authorization-server and GET /.well-known/openid-configuration return the same metadata, with Access-Control-Allow-Origin: * and Cache-Control: public, max-age=3600.

{
"issuer": "https://api.vivin.app",
"authorization_endpoint": "https://api.vivin.app/oauth/authorize",
"token_endpoint": "https://api.vivin.app/oauth/token",
"registration_endpoint": "https://api.vivin.app/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"scopes_supported": [
"bookings",
"listings",
"properties",
"tenants",
"maintenance",
"payments",
"bills",
"owners",
"analytics",
"channels",
"communication",
"support",
"scheduledReports",
"team"
]
}
  • Read the endpoint URLs from this document. Do not hard-code them.
  • The protected-resource metadata (RFC 9728) is served by the MCP server, not by the API. GET /.well-known/oauth-protected-resource on the API returns 404, like any other unknown /.well-known/* path.

Register a client​

POST /oauth/register registers a client. Send JSON.

Request fields​

NameTypeRequiredDescription
redirect_urisarrayYesAt least one URI, each allowed by the redirect URI policy
client_namestringNoShown on the consent page, as in "Authorize Claude". Cut to 255 characters. Without a name the page says "An MCP connector"
grant_typesarrayNoauthorization_code and/or refresh_token. Other values are ignored; with none (or only unsupported ones), both are used
token_endpoint_auth_methodstringNonone (default: a public client that uses PKCE) or client_secret_post
scopestringNoStored and echoed back. It does not limit access

Example request​

POST /oauth/register HTTP/1.1
Host: api.vivin.app
Content-Type: application/json

{
"client_name": "Claude",
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}

Response​

201 Created:

{
"client_id": "mcp-…",
"client_id_issued_at": 1790000000,
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"client_name": "Claude"
}

A client_secret_post client also receives client_secret. It is returned only in this response, so store it.

Errors​

Statuserrorerror_descriptionWhat to do
400invalid_redirect_uriredirect_uris must be a non-empty arraySend at least one redirect URI
400invalid_redirect_uriredirect_uri is not an allowed MCP client callback: <uri>Use an allowed URI, or an API key
400invalid_client_metadatagrant_types must be an arraySend an array
400invalid_client_metadataUnsupported token_endpoint_auth_method: <value>Use none or client_secret_post

Redirect URI policy​

Registration accepts only these redirect URIs:

ClientAccepted redirect_uri
Claudehttps://claude.ai/api/mcp/auth_callback
ChatGPThttps://chatgpt.com/connector_platform_oauth_redirect, or any path under https://chatgpt.com/connector/oauth/
Desktop and native clients (RFC 8252)http://localhost or http://127.0.0.1, any port and any path

Everything else is rejected, including other HTTPS domains, plain http to anything but loopback, and other schemes. A web client on another domain cannot register, so it should use an API key. On authorize and token, the redirect_uri must exactly match one the client registered; loopback URIs match on any port.

Authorize​

Open this URL in the landlord's browser, on one line, with every value URL-encoded:

https://api.vivin.app/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=<registered redirect URI>&code_challenge=<BASE64URL(SHA256(code_verifier))>&code_challenge_method=S256&state=<opaque value>&resource=<MCP server URL>

Query parameters​

NameRequiredDescription
response_typeYesMust be code
client_idYesFrom registration
redirect_uriYesOne of the client's registered URIs
code_challengeYesPKCE is mandatory
code_challenge_methodYesS256 only. plain is rejected
stateRecommendedReturned unchanged on the redirect
resourceNoThe MCP server URL (RFC 8707). It becomes the access token's audience (aud)
scopeNoAccepted, but it does not narrow the grant (Scopes)

What the landlord sees​

The API returns an HTML page titled Authorize plus the client's name, saying the client is requesting access to the landlord's VIVIN MCP. It never asks for a password: it identifies the landlord from their VIVIN session in that browser.

Landlord's statePage
Signed in to VIVINSigned in as with the email and company, and Sign out to switch accounts. Under "The following tools will be available:", the tools grouped by area, each area marked Read-only or Edit. Buttons Authorize and Cancel
Not signed in"You need to be signed in to VIVIN before you can authorize this connection.", with Sign in to VIVIN and Cancel. After the normal sign-in (emailed code or Google), VIVIN brings the landlord back here
No tools enabled"No MCP tools are enabled for this user yet.", and Authorize is disabled

A user has tools when their role has the Mcp permission (the Mcp row under Account Settings in Settings → Users → Role Permissions, see Users), AI access is on for them, and at least one area is Read-only or Edit in Settings → AI / MCP → Permissions. Out of the box, only the Admin and Super Admin roles have the Mcp permission.

The consent form expires 15 minutes after it opens.

Result​

The API redirects (302) to the redirect_uri with these query parameters:

OutcomeParameters
Landlord clicks Authorizecode, state. The code works once and expires after 120 seconds
Landlord clicks Cancelerror=access_denied, error_description=The landlord denied the authorization request, state
No VIVIN session when the form is submittederror=access_denied, error_description=No VIVIN session. Sign in to VIVIN, then start the connection again.
The user has no tools enablederror=access_denied, error_description=No MCP tools are enabled for this account. Enable at least one tool domain in Settings → AI / MCP first.
response_type is not codeerror=unsupported_response_type, error_description=response_type must be "code"
code_challenge is missingerror=invalid_request, error_description=code_challenge is required (PKCE is mandatory)
code_challenge_method is not S256error=invalid_request, error_description=code_challenge_method must be S256

Errors shown in the browser​

When the address is not safe to redirect to, the API shows an HTML error page with status 400 instead:

Page titleTextCause
Invalid request"Unknown or missing client_id."The client_id is not registered
Invalid request"The redirect_uri does not match a registered redirect URI for this client."The redirect_uri was not registered by that client
Session expired"This authorization form is invalid or has expired. Please start again."The landlord answered the consent form after 15 minutes

Token endpoint​

POST /oauth/token accepts application/x-www-form-urlencoded or JSON.

Request fields​

NameGrantRequiredDescription
grant_typeBothYesauthorization_code or refresh_token
client_idBothYesFrom registration
client_secretBothclient_secret_post onlyFrom registration
codeauthorization_codeYesFrom the redirect
redirect_uriauthorization_codeYesThe same URI as in the authorize request
code_verifierauthorization_codeYesThe PKCE verifier
refresh_tokenrefresh_tokenYesThe latest refresh token

Example requests​

POST /oauth/token HTTP/1.1
Host: api.vivin.app
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code>
&redirect_uri=<same redirect URI as in authorize>
&client_id=<client_id>
&code_verifier=<PKCE verifier>
POST /oauth/token HTTP/1.1
Host: api.vivin.app
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<client_id>

Response​

200 OK, with Cache-Control: no-store:

{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "<opaque token>",
"scope": "bookings listings properties tenants"
}
FieldDescription
access_tokenA VIVIN JWT. Send it to the MCP server as Authorization: Bearer <access_token>
expires_inSeconds until the access token expires, read from the token. Normally 900 (15 minutes): use the value you receive
refresh_tokenAn opaque token, valid for 60 days
scopeThe tool areas the user could use when they approved, separated by spaces. Informational only

Refresh rules​

  • Rotation: every refresh returns a new refresh token, valid for another 60 days, and the old one stops working. After 60 days without a refresh, the landlord must authorize again.
  • Reuse: a refresh token that was already used counts as stolen. VIVIN revokes the whole chain, and the landlord must authorize again. Two requests refreshing the same token at the same moment are different: one succeeds, the other gets invalid_grant, and nothing is revoked.
  • Access removed: if the user lost all AI tool access (AI access off, the role lost the Mcp permission, or every area is Off), the refresh fails with 403 access_denied. The refresh token is used up, so send the landlord through authorize again.
  • User deactivated: the refresh fails with invalid_grant.

Errors​

The register and token endpoints answer RFC 6749 JSON, not the usual VIVIN error body:

{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}
Statuserrorerror_descriptionWhat to do
400unsupported_grant_typeUnsupported grant_type: <value> (or (missing))Send authorization_code or refresh_token
400invalid_requestcode is required, code_verifier is requiredSend the field
400invalid_requestredirect_uri does not match a registered redirect URI for this clientSend a registered URI
400invalid_grantAuthorization code is invalid, Authorization code has already been used, Authorization code has expired, Authorization code was issued to a different clientStart the authorization again
400invalid_grantredirect_uri does not match the authorization request, PKCE verification failed, Unsupported PKCE code_challenge_methodSend the same URI and verifier as in authorize
400invalid_grantrefresh_token is required, Refresh token is invalid, Refresh token has expired, Refresh token was issued to a different clientAuthorize again
400invalid_grantRefresh token has been revoked (reuse detected)Authorize again. Refresh from one place only
400invalid_grantRefresh token was already used by a concurrent requestUse the token the other request received
400invalid_grantThe authorizing user is no longer active, Account mismatchThe landlord must authorize again
401invalid_clientUnknown client, Client authentication failedCheck client_id and client_secret
403access_deniedMCP access has been revoked for this userThe landlord must restore access, then authorize again
500server_errorInternal server errorRetry later

Registration errors are listed under Register a client, and authorize errors arrive on the redirect (Result). Past a rate limit, the API answers 429 with the standard VIVIN body (ThrottlerException: Too Many Requests) and a Retry-After header.

Using the access token​

  • The token only works for the VIVIN AI tools. Send it to the MCP server. On any other VIVIN API route it gets 403 with This access token may only be used for the VIVIN landlord AI tools.
  • Every tool call is checked against the user's current permissions: their role's Mcp permission, AI access and the areas in Settings → AI / MCP → Permissions. Changes apply to connected clients without a new authorization.
  • Write tools are currently limited to Scheduled reports; every other area is read-only. A few VIVIN product-help tools, which return no account data, are always available.
  • When the access token expires or is rejected, the MCP server answers 401 with WWW-Authenticate: Bearer error="invalid_token". Refresh the token and retry.
  • The MCP server uses the streamable HTTP transport and returns an Mcp-Session-Id header that the client sends on later requests. After a token refresh, or when a session times out, a request on the old session gets 404: start a new session. A 503 with Retry-After means VIVIN could not check permissions at that moment: wait and retry, do not refresh. Standard MCP clients handle all of this.

Scopes​

The scopes are the 14 tool areas of Settings → AI / MCP → Permissions. A grant always covers everything the user is allowed to use: the scope request parameter does not narrow it, and access is enforced by the live permissions, not by the scope string.

ScopeArea in SettingsCovers
bookingsBookingsBookings and their timeline, vacancy, upcoming move-ins, payment plans, sales occupancy and availability
listingsListingsUnits, calendars, rents and daily rates, manual blocks
propertiesPropertiesProperties and their units, smart locks, comments
tenantsTenantsTenants, service requests, surveys and answers
maintenanceMaintenanceTickets, today's check-ins and check-outs, check-in/out progress and steps, operations overview, inventory reports and setup
paymentsPaymentsPayments, transactions, payouts, deposits, discounts, invoicing settings
billsBillsUtility bills, cost map, cash flows
ownersOwnersOwners and owner reports
analyticsAnalyticsRevenue, occupancy, debt, finance overview, planning targets
channelsChannelsChannel and OTA connection status
communicationCommunicationAutomatic message rules and messages, notifications, email and WhatsApp conversations
supportSupportYour support tickets with VIVIN, subscription
scheduledReportsScheduled reportsReports the AI schedules for you
teamTeamUsers and role permissions

Revoking access​

There is no revoke endpoint. To cut off a connected client:

  • Switch AI access off for that user under Per-user permissions in Settings → AI / MCP → Permissions. The client's next tool call is refused, and so is its next refresh. Switching it back on before the client refreshes restores the connection.
  • Or remove the Mcp permission from the user's role, or set every area to Off.
  • Removing the connector in Claude or ChatGPT stops that client. Its refresh token is not revoked, but it expires after 60 days without use.

Troubleshooting​

SymptomLikely cause and fix
invalid_redirect_uri on registrationThe client's callback is not on the allowed list. Use an API key from Settings → AI / MCP → API keys
The consent page shows no tools and Authorize is disabledThe user lacks the Mcp permission or AI access, or every area is Off
invalid_grant Authorization code has expiredThe code was exchanged more than 120 seconds after the redirect. Exchange it at once
The connection breaks after about 60 daysThe client never refreshed. Refresh before the access token expires, and keep the newest refresh token
403 on a VIVIN API routeMCP tokens only work on the MCP server