Operto Teams has no official MCP server as of July 2026, and no Operto Teams app appears in Zapier's public directory. The way to connect Operto Teams to Claude is its public REST API, the same API it has carried since the VRScheduler days: request access by emailing support@operto.com, create an API key under Setup then API Keys, exchange the key for a bearer token at the oauth login endpoint, and wrap the GET endpoints in a small custom MCP server. This guide walks through the whole thing and ships the server code. One limit to know up front: Operto Teams captures checklist video, but the public API is image-only, so video never leaves the platform through the API.
The video gap: what Operto Teams captures vs what its API exposes
Operto Teams is unusual among STR operations platforms in one specific way: it captures video natively. According to the Operto Teams Knowledge Base (Task Form Examples), checklist Task Forms support Video Upload and Multiple Video Upload element types alongside image uploads, grids, and text inputs. According to the Operto Teams System Settings documentation, staff can also attach video to issue reports from their dashboards ("Allow Video Upload" in the Issue Form), with a maximum of 1 GB per video. Cleaners on Operto Teams are, in many operations, already recording walkthrough video every turnover.
The public API tells a different story. Media goes in through Create Task With Image (POST /api/v1/tasksWithImage, multipart form data, files named Image1, Image2, and so on) and through image attachments on issues. Task responses include a TaskImages array. No video endpoint is documented anywhere in the API reference at teams.operto.com/api.
- Video Upload checklist element Task Form element type, per the Task Form Examples article
- Multiple Video Upload checklist element Same source; sits alongside image and grid elements
- Issue Form video from staff dashboards "Allow Video Upload" system setting, max 1 GB per video
- Photos via image upload and image verification elements
- Images only.
POST /api/v1/tasksWithImageand image attachments on issues - TaskImages array on task responses
- No video endpoint documented, in or out
So this MCP server gives Claude your operational data: schedules, completions, hours, issues, task metadata. The walkthrough video your team records is a separate conversation, and we come back to it at the end of this guide.
What you'll be able to ask Claude
Once connected, Claude can pull from the same endpoints your operations dashboard uses. Three clusters matter most for a cleaning ops manager:
Prerequisites
- Operto Teams account with API access. According to the Operto Teams Knowledge Base, API access is requested by emailing
support@operto.com. It is not self-serve, so send that email first; the rest of this guide waits on it. - Claude Desktop installed, or the Claude Code CLI. Download Claude Desktop.
- Node.js 18 or later. Check with
node --version.
Request API access and create an API key
Operto Teams gates its public API behind a support request, but once enabled, key management is yours, and it is more granular than most STR APIs.
- Email
support@operto.comand ask for Operto Teams API access on your account. - Once enabled, go to Setup then API Keys in your Operto Teams dashboard.
- Create a key with an internal name. This is the useful part: each key can be restricted to only the resources it needs. For this guide's read-only server, grant read access to Properties, Tasks, Staff Tasks, Staff Task Times, and Issues, and leave everything else off.
- Save, then copy the two credentials: the APIKey and its Value. You need both.
The API reference lives at teams.operto.com/api. If you found this page searching for the VRScheduler API: same thing. Operto Teams is the former VRScheduler, the identical documentation is also served at vrscheduler.com/api, and the auth header prefix is still literally VRS.
Set up the Node.js project
Two dependencies: the MCP SDK and zod. Operto Teams has no official SDK, so we call the REST API directly.
mkdir operto-teams-mcp
cd operto-teams-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
Add "type": "module" to package.json:
{
"name": "operto-teams-mcp",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0"
}
}
Create the MCP server file
Two things make this server different from the others in this series. First, auth is a two-step token exchange: you POST your key pair to /api/v1/oauth/login, get back an access token (expires in 1 hour) and a refresh token (24 hours), and send the access token as Authorization: VRS <token> on every call. The server below just logs in again when the token is near expiry, which is simpler than juggling refresh tokens. Second, the API is rate limited to 50 requests per 2 minutes per account, so let Claude know when it should batch its thinking rather than hammering list endpoints.
Save the following as index.js. Six read-only tools: list-properties, list-tasks, get-task, get-task-form-elements, list-staff-tasks, and list-issues.
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_KEY = process.env.OPERTO_TEAMS_API_KEY;
const API_VALUE = process.env.OPERTO_TEAMS_API_VALUE;
const BASE_URL = "https://teams-api.operto.com";
if (!API_KEY || !API_VALUE) {
console.error("Missing OPERTO_TEAMS_API_KEY or OPERTO_TEAMS_API_VALUE");
process.exit(1);
}
// --- Auth: exchange the key pair for a bearer token, re-login near expiry ---
let cachedToken = null;
let tokenFetchedAt = 0;
async function getToken() {
// Access tokens last 1 hour; re-login after 50 minutes to be safe.
if (cachedToken && Date.now() - tokenFetchedAt < 50 * 60 * 1000) {
return cachedToken;
}
const response = await fetch(`${BASE_URL}/api/v1/oauth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ API_Key: API_KEY, API_Value: API_VALUE }),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`Operto Teams login failed ${response.status}: ${body}`);
}
const data = await response.json();
cachedToken = data?.Access_Token?.token;
if (!cachedToken) {
throw new Error(
`Login succeeded but no Access_Token.token in response: ${JSON.stringify(data)}`
);
}
tokenFetchedAt = Date.now();
return cachedToken;
}
async function opertoGet(path, params = {}) {
const token = await getToken();
const url = new URL(`${BASE_URL}${path}`);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && value !== "") {
url.searchParams.append(key, String(value));
}
}
const response = await fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `VRS ${token}`,
Accept: "application/json",
"User-Agent": "rapideye-operto-teams-mcp/1.0",
},
});
if (response.status === 429) {
throw new Error(
"Operto Teams rate limit hit (50 requests per 2 minutes). Wait before retrying."
);
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Operto Teams API ${response.status}: ${body}`);
}
return response.json();
}
const asText = (data) => ({
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
});
const server = new McpServer({
name: "operto-teams",
version: "1.0.0",
});
// Tool 1: Properties
server.tool(
"list-properties",
"List properties in Operto Teams. Returns property IDs, names, and settings. Use this first to map PropertyIDs for other queries.",
{},
async () => asText(await opertoGet("/api/v1/properties"))
);
// Tool 2: Tasks (the workhorse)
server.tool(
"list-tasks",
"List tasks (cleans, inspections, maintenance) with optional filters. Each task includes TaskID, PropertyID, TaskName, TaskDate, Completed/Approved flags, assigned Staff, and a TaskImages array. Rate limit is 50 requests per 2 minutes, so prefer date-bounded queries over paging through everything.",
{
PropertyID: z.string().optional().describe("Filter to one property"),
Completed: z.string().optional().describe("1 for completed tasks, 0 for not completed"),
TaskStartDate: z.string().optional().describe("Earliest task date, YYYY-MM-DD"),
TaskEndDate: z.string().optional().describe("Latest task date, YYYY-MM-DD"),
CompletedStartDate: z.string().optional().describe("Earliest completion date, YYYY-MM-DD"),
CompletedEndDate: z.string().optional().describe("Latest completion date, YYYY-MM-DD"),
per_page: z.string().optional().describe("Results per page"),
page: z.string().optional().describe("Page number"),
},
async (params) => asText(await opertoGet("/api/v1/tasks", params))
);
// Tool 3: Single task
server.tool(
"get-task",
"Fetch one task by TaskID, including its TaskImages array and assigned staff.",
{ TaskID: z.string().describe("Task ID from list-tasks") },
async ({ TaskID }) => asText(await opertoGet(`/api/v1/tasks/${TaskID}`))
);
// Tool 4: Task form elements (the checklist)
server.tool(
"get-task-form-elements",
"Fetch the checklist (Task Form) elements for a task. Useful for reviewing what the checklist asked and what staff recorded. Note: the public API does not expose checklist video; video captured in Operto Teams stays in the platform.",
{ TaskID: z.string().describe("Task ID from list-tasks") },
async ({ TaskID }) =>
asText(await opertoGet(`/api/v1/tasks/${TaskID}/formElements`))
);
// Tool 5: Staff task assignments and hours
server.tool(
"list-staff-tasks",
"List staff task assignments with optional filters. Cross-reference with list-tasks for workload and completion analysis.",
{
StaffID: z.string().optional().describe("Filter to one staff member"),
PropertyID: z.string().optional().describe("Filter to one property"),
Completed: z.string().optional().describe("1 for completed, 0 for not completed"),
TaskStartDate: z.string().optional().describe("Earliest task date, YYYY-MM-DD"),
TaskEndDate: z.string().optional().describe("Latest task date, YYYY-MM-DD"),
per_page: z.string().optional().describe("Results per page"),
page: z.string().optional().describe("Page number"),
},
async (params) => asText(await opertoGet("/api/v1/stafftasks", params))
);
// Tool 6: Issues
server.tool(
"list-issues",
"List reported issues (damage, maintenance, missing items) logged by staff.",
{},
async () => asText(await opertoGet("/api/v1/issues"))
);
const transport = new StdioServerTransport();
await server.connect(transport);
The endpoint paths, auth flow, and query parameters above come from the public API reference at teams.operto.com/api. If a filter behaves differently on your account, that reference is the source of truth.
Register the server with Claude Desktop
Open your Claude Desktop config file:
~/Library/Application Support/Claude/claude_desktop_config.json
%APPDATA%\Claude\claude_desktop_config.json
Add the Operto Teams entry (merge into your existing mcpServers block if you've set up other guides from this series):
{
"mcpServers": {
"operto-teams": {
"command": "node",
"args": ["/absolute/path/to/operto-teams-mcp/index.js"],
"env": {
"OPERTO_TEAMS_API_KEY": "your_api_key_here",
"OPERTO_TEAMS_API_VALUE": "your_api_value_here"
}
}
}
}
Replace the path and both credentials with your real values, then fully quit and reopen Claude Desktop.
Try it
Troubleshooting
Login fails with 401 or an error about credentials
Three usual causes. First, API access may not be enabled on your account yet; it requires emailing support@operto.com and is not on by default. Second, check that you copied both halves of the credential: the login body needs API_Key and API_Value as separate fields. Third, confirm the key wasn't created with resource restrictions that exclude the endpoint you're hitting.
Requests worked, then started failing after about an hour
Access tokens expire after 1 hour. The server in this guide re-logs-in automatically after 50 minutes, but if you modified it, make sure the token cache invalidates. Also confirm the header format is exactly Authorization: VRS <token>, with the VRS prefix. That prefix is a VRScheduler leftover and it is required.
429 or throttling errors on bigger questions
The API allows 50 requests per 2 minutes per account. Broad questions ("analyze all tasks ever") can burn through that fast if Claude pages through results. Ask date-bounded questions, or tell Claude explicitly to fetch one date range in as few calls as possible and analyze from there.
Claude says it doesn't see any Operto Teams tools
Claude Desktop only reads the config file at startup. Fully quit Claude (Cmd+Q on macOS, right-click tray icon then Quit on Windows) and reopen it. Closing the window doesn't reload the config.
Still broken? Check Help, then Open Logs Folder for a startup error. Common causes: invalid JSON in the config file, wrong absolute path to index.js, or node not on your PATH.
I want Claude to see the checklist videos my cleaners record
The public API can't do it. Video captured through Task Form video elements or the Issue Form lives inside Operto Teams, and the API's media surface is image-only (tasksWithImage, issue images, TaskImages). Your options: download videos manually from the dashboard and attach them to Claude directly (Claude can analyze video frames from uploads in supported clients), or use a purpose-built analysis layer. That second option is what RapidEye does; see the note below.
FAQ
Does Operto Teams have an official MCP server?
No. As of July 2026, Operto has not published an MCP server for Operto Teams, and there is no Operto Teams app in Zapier's public directory either. The public REST API plus a small custom MCP server, like the one on this page, is the practical route to Claude.
Is the VRScheduler API the same as the Operto Teams API?
Yes. Operto Teams is the former VRScheduler, and the API still shows it: the identical public API documentation is served at both teams.operto.com/api and vrscheduler.com/api, and the bearer token header uses the prefix VRS. Old VRScheduler integration write-ups generally still describe the right API shape.
Does the Operto Teams public API support video?
No. The product captures video (Task Form Video Upload and Multiple Video Upload elements, plus Issue Form video at up to 1 GB per file per the System Settings documentation), but the API's media endpoints are image-only. There is no documented endpoint to upload or retrieve video.
How do I get Operto Teams API access?
Email support@operto.com to request it, per the Operto Teams Knowledge Base. Once enabled, create credentials under Setup, then API Keys, where each key can be limited to specific resources. The key pair is exchanged for a 1-hour bearer token at POST /api/v1/oauth/login.
Can Claude change my Operto Teams schedule with this setup?
Not with this guide's server; it wraps GET endpoints only. The API itself does support writes (creating tasks, issues, property bookings, and properties), so write tools are possible. If you add them, keep them in a separate server from your read-only one, and consider scoping a dedicated API key to just the resources the write server needs.
About all that walkthrough video
If your cleaners are recording checklist video in Operto Teams, you're sitting on the most information-dense documentation an STR operation produces, and in most operations nobody watches it. A one-minute walkthrough holds hundreds of frames of property condition that a checklist checkbox flattens into "done."
RapidEye is inspection intelligence built for exactly this: it analyzes turnover photos and walkthrough videos with AI to catch damage, missing items, and quality issues that human review misses. If your team already captures video every turnover, you have the hard part done. See the RapidEye API for wiring inspection intelligence into your stack, or talk to us about analyzing your existing walkthrough footage.
Other guides
This is one of a series on connecting the STR stack to Claude. Browse the full set at Claude Guides.
Connect PriceLabs to Claude
Query synced listings and dynamic price recommendations through the Customer API.
Connect Hostaway to Claude
One of the PMSs Operto Teams syncs bookings from. Reservations, listings, and messages.
Connect OwnerRez to Claude
Also feeds Operto Teams. Bookings, properties, guests, and payments.
Sources & references
- Public API Documentation for Operto Teams https://teams.operto.com/api/
- Public API - Operto Teams Knowledge Base https://help-teams.operto.com/article/461-public-api
- Task Form Examples - Operto Teams Knowledge Base https://help-teams.operto.com/article/239-task-form-examples
- System Settings - Operto Teams Knowledge Base https://help-teams.operto.com/article/193-site-settings
- Public API Documentation (vrscheduler.com mirror) https://www.vrscheduler.com/api/
- Operto Teams - Housekeeping Services - OwnerRez Support https://www.ownerrez.com/support/articles/operto-teams
- Model Context Protocol TypeScript SDK https://github.com/modelcontextprotocol/typescript-sdk

