Vai al contenuto principale

Management session authentication

A management session lets your own script sign in as one of your VIVIN users and call the API with that user's access, the same way the VIVIN web app does.

Before you start​

ItemValue
Who it is forLandlords and property managers automating their own account. Booking platforms use a partner token; AI clients use MCP OAuth
Base URLhttps://api.vivin.app
You needA VIVIN user with a password, and access to that user's mailbox for the emailed code
AccessLimited to the user's account and to their role's permissions in Settings → Users → Role Permissions
RoutesThe routes the VIVIN web app uses. They are not versioned, and VIVIN does not publish a reference for them
Management sessionPartner token
Who uses itYour VIVIN users, the web app and your own scriptsBooking platforms and other partners
How you get itEmail and password, then a code sent by emailVIVIN issues one per platform
What it opensThe app's routes, limited to your account and the user's roleOnly partner routes (/<prefix>/…)
LifetimeA 15-minute access token, renewed with a refresh cookie, for up to 30 daysSet by VIVIN when it issues the token

A partner token does not work on management routes, and a management token does not work on partner routes.

Endpoints​

MethodPathWhat it does
POST/auth/loginChecks the email and password, then emails a code
POST/auth/verify-otpChecks the code and starts the session
POST/auth/resend-otpEmails a new code for the same sign-in
GET/auth/meReturns the signed-in user, their account and preferences
POST/auth/refreshExchanges the refresh cookie for a new access token
POST/auth/logoutEnds the session

Sign in​

POST /auth/login checks the password. Usually it then emails a 6-digit code to the user.

Request fields​

NameTypeRequiredDescription
emailstringYesThe user's email address
passwordstringYesAt least 6 characters

Example request​

curl -sS -b vivin-cookies.txt -c vivin-cookies.txt \
-X POST https://api.vivin.app/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"your-password"}'

Response​

201 Created, asking for the code:

{
"otpRequired": true,
"challengeToken": "<challenge-token>",
"expiresAt": "2026-09-26T10:10:00.000Z",
"maskedEmail": "y••@example.com"
}
  • The code is valid for 10 minutes. Signing in again cancels the earlier code.
  • When the request carries this user's remembered-browser cookie (Verify the code), there is no code: the response is already the session, with the body shown under Verify the code, and the refresh cookie is set.

Errors​

StatusmessageCauseWhat to do
400email must be an email, password must be longer than or equal to 6 charactersA malformed field. Unknown fields also return 400Fix the body
401Invalid credentialsWrong email or password, or the user never set a passwordCheck them. A user without a password sets one with Forgot password?
401Account deactivatedThe user is deactivatedAsk an admin to reactivate the user
429Too many sign-in attempts for this account. Wait a few minutes and try again.Too many wrong passwords for this email, from your network or overall, in 15 minutes. The body adds retryAfterSeconds and errorCode: "TOO_MANY_LOGIN_ATTEMPTS"Wait retryAfterSeconds, then sign in again
429Too many login codes requested. Please wait before trying again.5 codes were already sent to this user in the last 15 minutesWait a few minutes
429ThrottlerException: Too Many RequestsMore than 10 sign-in requests from your IP address in a minuteWait the seconds in the Retry-After header

Verify the code​

POST /auth/verify-otp completes the sign-in with the emailed code.

Request fields​

NameTypeRequiredDescription
challengeTokenstringYesThe value from POST /auth/login
otpstringYesThe 6-digit code from the email, as a string
rememberDevicebooleanNotrue sets a remembered-browser cookie, so later sign-ins with the same cookie jar skip the code for 30 days. It matches Don't ask for a code on this browser for 30 days on the Check your email screen

Example request​

curl -sS -b vivin-cookies.txt -c vivin-cookies.txt \
-X POST https://api.vivin.app/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{"challengeToken":"<challenge-token>","otp":"481920","rememberDevice":true}'

Response​

200 OK with the session:

{
"token": "<access-token>",
"user": {
"id": "…",
"email": "[email protected]",
"firstName": "Casey",
"lastName": "Sample",
"role": "admin",
"accountId": "…",
"ownerId": null,
"hasProperties": null
},
"account": { "id": "…", "companyName": "…", "logoUrl": "…" }
}
FieldDescription
tokenThe access token for your API calls, valid for 15 minutes
userThe signed-in user. ownerId and hasProperties are filled only for owner users
accountThe user's account

The response also sets an httpOnly refresh cookie. Keep it: you need it to refresh.

Errors​

Code errors carry a reason next to the usual fields:

{
"reason": "invalid_code",
"statusCode": 401,
"message": "That code is not valid. Request a new one and try again.",
"error": "UnauthorizedException"
}
Statusmessage and reasonCauseWhat to do
400otp must contain digits only, otp must be longer than or equal to 6 characters and similarA malformed fieldSend the 6 digits as a string
401That code is not valid. Request a new one and try again., reason: invalid_codeWrong code. Each code allows 5 triesCheck the code and send it again
401Same message, reason: too_many_attempts, expired, consumed or not_foundThis sign-in attempt is overStart again at POST /auth/login
401Session is no longer validThe user was deactivated between the two stepsAsk an admin
429ThrottlerException: Too Many RequestsMore than 10 requests from your IP address in a minuteWait the seconds in Retry-After

Send a new code​

POST /auth/resend-otp emails a new code for the same sign-in, like Send a new code in the app. The new code replaces the old one and gets a fresh 10 minutes and 5 tries, up to 3 times per sign-in.

Request fields​

NameTypeRequiredDescription
challengeTokenstringYesThe value from POST /auth/login

Response​

200 OK with { "message": "A new code is on its way.", "expiresAt": "<timestamp>" }.

Errors​

Statusmessage and reasonCauseWhat to do
401That sign-in attempt has expired. Go back and enter your password again., reason: too_many_resendsThree new codes were already sentUse the last code, or start again at POST /auth/login
401Same message, reason: expired, too_many_attempts, consumed or not_foundThis sign-in attempt is overStart again at POST /auth/login
429ThrottlerException: Too Many RequestsMore than 5 requests from your IP address in a minuteWait the seconds in Retry-After

Call the API​

Send the access token in the Authorization header of every request.

curl https://api.vivin.app/auth/me \
-H "Authorization: Bearer <access-token>"

GET /auth/me returns the signed-in user, their account and preferences: a good first call to check the token.

Most routes and GET /auth/me word their 401s differently:

Statusmessage (most routes)message (/auth/me)CauseWhat to do
401Missing Authorization headerMissing authentication credentialsNo headerAdd it
401Invalid Authorization header formatInvalid authentication credentialsThe header is not Bearer <token>Fix the header
401UnauthorizedInvalid authentication credentialsThe token expired or is not validRefresh, then retry once
401Your session has ended.Your session has ended.The session was signed out, or the user's password was changed or resetSign in again
403Forbidden resource—The user's role lacks the permission the route needs, or the user was deactivatedAsk an admin

Refresh the access token​

The access token lasts 15 minutes. POST /auth/refresh, with the refresh cookie and no body, returns a new one.

curl -sS -b vivin-cookies.txt -c vivin-cookies.txt \
-X POST https://api.vivin.app/auth/refresh

Response​

201 Created with the same body as Verify the code and a new token. It also replaces the refresh cookie.

  • Save the new cookie every time: each refresh cookie works once.
  • A replaced refresh cookie sent again more than 30 seconds later is treated as stolen and ends the session. Within 30 seconds (two calls racing), the second call also succeeds.
  • A session ends 30 days after the sign-in, however often you refresh. Then sign in again, with the code unless the remembered-browser cookie is still valid.
  • An empty body sent with Content-Type: application/json is accepted.

Errors​

StatusmessageCauseWhat to do
401No refresh tokenThe cookie was not sentSend the cookie jar
401Invalid or expired refresh tokenThe cookie is unknown, expired, or the 30 days are overSign in again
401Refresh token reuse detectedA replaced cookie was sent again: the session is endedSign in again
401Session is no longer validThe user was deactivated or removedAsk an admin
429ThrottlerException: Too Many RequestsMore than 60 refreshes from your IP address in a minuteWait and retry

Sign out​

POST /auth/logout, with the refresh cookie and no body, ends the session and clears the cookie. It always returns 200 with {"message":"Logged out"}, even when the access token has already expired.

Google sign-in​

The Google button on the VIVIN sign-in page uses a Google ID token issued for VIVIN's own page. It skips the emailed code and only signs in users who already exist in VIVIN. Scripts cannot get such a token, so they sign in with email and password; a user who signs in only with Google sets a password first with Forgot password?.

Full example​

This bash script signs in, asks for the emailed code, calls the API, refreshes the token and signs out. It needs curl and jq.

API=https://api.vivin.app
JAR=vivin-cookies.txt # holds the session: keep it private

# 1. Email and password (sends the remembered-browser cookie, if any)
curl -sS -b "$JAR" -c "$JAR" -X POST "$API/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"your-password"}' > session.json

# 2. The emailed code, only when VIVIN asks for it
if [ "$(jq -r '.otpRequired // false' session.json)" = "true" ]; then
read -r -p "Code sent to $(jq -r .maskedEmail session.json): " CODE
jq -n --arg t "$(jq -r .challengeToken session.json)" --arg c "$CODE" \
'{challengeToken: $t, otp: $c, rememberDevice: true}' \
| curl -sS -b "$JAR" -c "$JAR" -X POST "$API/auth/verify-otp" \
-H 'Content-Type: application/json' -d @- > session.json
fi

TOKEN=$(jq -r .token session.json)

# 3. Call the API
curl -sS "$API/auth/me" -H "Authorization: Bearer $TOKEN"

# 4. After 15 minutes, get a new access token
TOKEN=$(curl -sS -b "$JAR" -c "$JAR" -X POST "$API/auth/refresh" | jq -r .token)

# 5. End the session
curl -sS -b "$JAR" -c "$JAR" -X POST "$API/auth/logout"

Limits​

RouteRequests per IP address and minute
POST /auth/login, /auth/verify-otp10
POST /auth/resend-otp5
POST /auth/logout30
POST /auth/refresh60
Most other management routes600

Past a limit, the API answers 429 ThrottlerException: Too Many Requests with a Retry-After header. Wrong passwords also count per email address, across networks (Sign in).

Troubleshooting​

SymptomLikely cause and fix
Every sign-in from your script asks for a codeThe cookie jar is not kept between runs, or rememberDevice was not true. Keep the jar private and reuse it
Refresh token reuse detected after a whileTwo machines or processes share one refresh cookie. Refresh from one place only
Your session has ended. on every callThe user's password was changed or reset, which ends every session and forgets remembered browsers. Sign in again
403 Forbidden resourceThe user's role does not allow that action. Use a user with the right role
Invalid credentials for a user who signs in with GoogleThe user has no password. Set one with Forgot password?