Where Track stands, July 2026
Track describes its developer hub as a public version of its internal API documentation, and notes that full API support and testing environments are reserved for cases where "we have a mutual customer under contract." For your own account with your own Server key, the read endpoints in this guide are self-serve. Track's own marketing calls the platform "API-first," and the 75+ integration partners on its marketplace all ride the same API surface you're about to use.
What Track's API exposes
This is the part most operators underestimate. Track's API is not a listings feed; it mirrors the operational core of the PMS. According to the Airbyte project, whose production Track PMS connector syncs against this API, the surface spans 90-plus resource collections. The ones that matter for daily ops:
Reservations
GET /api/pms/reservationsFilter by booked, arrival, or departure date ranges, unit, status (Hold, Confirmed, Checked In, Checked Out, Cancelled), and updatedSince.
Units
GET /api/pms/unitsSearch by name, node, unit type, amenity, active status, and availability window (arrival/departure dates).
Housekeeping
GET /api/pms/housekeeping/work-ordersHousekeeping work orders, plus clean types and task lists on sibling endpoints. The turnover pipeline, queryable.
Maintenance
GET /api/pms/maintenance/work-ordersMaintenance work orders with an updatedSince filter, plus a maintenance problems catalog at /api/pms/maintenance/problems.
Owners
GET /api/pms/ownersOwner records, contracts, statements, and owner-to-unit mappings on related endpoints.
Folios & accounting
GET /api/pms/foliosFolios, folio transactions, charges, and a full accounting family (accounts, bills, deposits, transactions).
All endpoints hang off your own subdomain, https://yourcompany.trackhs.com/api/, and answer to HTTP Basic auth with your Server API key as the username and the secret as the password. This guide wires up the four in the left column; add more by copying the pattern.
What you can ask once it's connected
Written for the ops manager running a large portfolio, because that is who runs Track:
- "How does this Saturday's checkout count compare to the housekeeping work orders already created? Is any unit uncovered?"
- "Which 10 units generated the most maintenance work orders in the last 90 days?"
- "List arrivals in the next 7 days at units that still have an open maintenance work order."
- "Pull maintenance work orders updated this week and summarize them by problem type."
- "Which active units have no reservation in the next 30 days?"
Before you start
Three things: a Track account with permission to create API keys (Configuration access), Node.js 18 or newer (check with node --version), and either Claude Desktop or Claude Code.
Create a Server API key in Track
In Track, open Configuration → Company Setup → API Keys and click the + Server Key button. Copy the Server API Key and Secret. This is the same self-serve path Track's integration partners (Operto Teams, for example) walk their mutual customers through.
Scaffold the project
Make a folder and install the two dependencies: the official MCP SDK and zod (used to describe each tool's inputs). No build step.
mkdir track-mcp && cd track-mcp
npm init -y
npm pkg set type="module"
npm install @modelcontextprotocol/sdk zod
Write the server
Save this as index.js. Auth is plain HTTP Basic (key as username, secret as password), so there is no token dance at all; simpler than most PMS APIs. Track returns HAL-style JSON with the rows under _embedded, and pages with page and size parameters.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const DOMAIN = process.env.TRACK_DOMAIN; // e.g. yourcompany.trackhs.com
const KEY = process.env.TRACK_API_KEY;
const SECRET = process.env.TRACK_API_SECRET;
if (!DOMAIN || !KEY || !SECRET) {
console.error("Set TRACK_DOMAIN, TRACK_API_KEY and TRACK_API_SECRET first.");
process.exit(1);
}
// Track uses HTTP Basic auth: API key as username, secret as password.
const AUTH = "Basic " + Buffer.from(`${KEY}:${SECRET}`).toString("base64");
async function get(path, params = {}) {
const url = new URL(`https://${DOMAIN}/api/${path}`);
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null && v !== "") url.searchParams.set(k, String(v));
}
const res = await fetch(url, {
headers: { Authorization: AUTH, Accept: "application/json" },
});
if (!res.ok) throw new Error(`Track ${res.status}: ${await res.text()}`);
return res.json();
}
function json(data) {
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
const paging = {
page: z.number().optional().describe("Page number, starts at 1"),
size: z.number().optional().describe("Rows per page, e.g. 100"),
};
const server = new McpServer({ name: "track", version: "1.0.0" });
server.registerTool(
"search_units",
{
description: "Search units in Track. Rows come back under _embedded.units.",
inputSchema: {
...paging,
search: z.string().optional().describe("Substring match on unit name or description"),
isActive: z.number().optional().describe("1 for active units, 0 for inactive"),
arrival: z.string().optional().describe("Availability window start, YYYY-MM-DD"),
departure: z.string().optional().describe("Availability window end, YYYY-MM-DD"),
},
},
async (args) => json(await get("pms/units", args))
);
server.registerTool(
"search_reservations",
{
description:
"Search reservations. Statuses: Hold, Confirmed, Checked In, Checked Out, Cancelled. Rows under _embedded.reservations.",
inputSchema: {
...paging,
status: z.string().optional().describe("Reservation status filter"),
unitId: z.number().optional().describe("Filter to one unit"),
arrivalStart: z.string().optional().describe("Earliest arrival, YYYY-MM-DD"),
arrivalEnd: z.string().optional().describe("Latest arrival, YYYY-MM-DD"),
departureStart: z.string().optional().describe("Earliest departure, YYYY-MM-DD"),
departureEnd: z.string().optional().describe("Latest departure, YYYY-MM-DD"),
updatedSince: z.string().optional().describe("Only rows updated since this ISO date"),
},
},
async (args) => json(await get("pms/reservations", args))
);
server.registerTool(
"housekeeping_work_orders",
{
description: "List housekeeping work orders (the turnover clean pipeline). Rows under _embedded.workOrders.",
inputSchema: { ...paging },
},
async (args) => json(await get("pms/housekeeping/work-orders", args))
);
server.registerTool(
"maintenance_work_orders",
{
description: "List maintenance work orders. Rows under _embedded.workOrders.",
inputSchema: {
...paging,
updatedSince: z.string().optional().describe("Only rows updated since this ISO date"),
},
},
async (args) => json(await get("pms/maintenance/work-orders", args))
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Track MCP server running on stdio");
Connect it to Claude
Point Claude at the server and pass your three values.
Add a track entry to your config file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %AppData%\Claude\claude_desktop_config.json on Windows), then fully quit and reopen the app.
{
"mcpServers": {
"track": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/track-mcp/index.js"],
"env": {
"TRACK_DOMAIN": "yourcompany.trackhs.com",
"TRACK_API_KEY": "your-server-api-key",
"TRACK_API_SECRET": "your-server-api-secret"
}
}
}
}
One command from your terminal. Everything after -- is what launches the server.
claude mcp add \
--env TRACK_DOMAIN=yourcompany.trackhs.com \
--env TRACK_API_KEY=your-server-api-key \
--env TRACK_API_SECRET=your-server-api-secret \
--transport stdio track \
-- node /ABSOLUTE/PATH/TO/track-mcp/index.js
Confirm with claude mcp list. Add --scope user to make it available in every project.
Ask away
Start a new conversation. Claude asks permission the first time it calls a tool, approve it, and you're set.
Which reservations arrive in the next 7 days? Group them by unit.
Pull maintenance work orders updated in the last 30 days and rank units
by how many they generated.
Compare Saturday's departures against housekeeping work orders and flag
any unit without one.
Prefer to skip the typing?
Paste this into Claude Code (or any coding agent) and it will build, install, and wire up the server for you. Then come back to step 1 for your credentials.
Build a local MCP server (Node.js, ESM, @modelcontextprotocol/sdk and zod)
for Track PMS (trackhs.com). Read TRACK_DOMAIN (like
yourcompany.trackhs.com), TRACK_API_KEY and TRACK_API_SECRET from the
environment. Auth is HTTP Basic: key as username, secret as password, sent
as an Authorization header on every request. Base URL is
https://TRACK_DOMAIN/api/. Responses are HAL-style JSON with rows under
_embedded, and paginate with page and size query params. Register these
read-only GET tools built with URL/searchParams:
search_units -> pms/units (page, size, search, isActive, arrival,
departure), search_reservations -> pms/reservations (page, size, status,
unitId, arrivalStart, arrivalEnd, departureStart, departureEnd,
updatedSince), housekeeping_work_orders -> pms/housekeeping/work-orders
(page, size), maintenance_work_orders -> pms/maintenance/work-orders
(page, size, updatedSince). Use StdioServerTransport; log startup to
stderr. Then make a package.json (type: module), install the deps, and
show me the claude mcp add command, the claude_desktop_config.json block,
and three prompts to try.
Good to know
- Mind the rate limit. Track enforces roughly 10,000 requests per 5 minutes; Airbyte's production connector backs off for 5 minutes when it hits a 429. A chat session will never get close, but if you later script bulk pulls, add backoff on 429s.
- Rows live under _embedded. Track's API returns HAL-style JSON: the actual records sit under _embedded.units, _embedded.reservations, or _embedded.workOrders, with paging links under _links. Claude handles this fine, but it explains why responses look nested.
- It's read-only. Every tool GETs. Claude can't create work orders, edit reservations, or touch folios. If you add writes later, register them as separate deliberate tools and test against non-live data first.
- Docs are public, support is partner-gated. developer.trackhs.com is a public version of Track's internal API documentation, and Track notes full API support comes with a mutual customer under contract. For deeper integration work, Track's team and its 75+ listed integration partners are the official route.
- The API goes much further than these four tools. Owners, folios, accounting, CRM contacts, custom fields, reviews. Copy the registerTool pattern, point it at the endpoint in the reference, and grow the surface as questions come up.
The work-order data has a blind spot: what the photos show. Track tells you a clean happened and a work order closed; it can't tell you the glass shower door is cracked in frame two of the turnover photos. RapidEye reads the inspection photos your housekeepers already capture and flags damage and missed cleaning before the next check-in, and its API returns those findings to whatever system you run, Track included.
FAQ
Does Track have an MCP server?
No. As of July 2026 there is no official MCP server from Track or TravelNet Solutions, and we found no community Track MCP server on GitHub or in the MCP registries. The build-it-yourself path above is the practical route, and it's a short build because Track's auth is plain Basic auth.
Does Track have a public API?
Yes. The documentation lives at developer.trackhs.com, described by Track as a public version of its internal API documentation. Endpoints hang off your own subdomain (https://yourcompany.trackhs.com/api/) and cover reservations, units, housekeeping, maintenance, owners, folios, accounting, and CRM data.
What credentials do I need?
A Server API key and secret, created self-serve in Track under Configuration → Company Setup → API Keys with the + Server Key button. Requests authenticate with HTTP Basic auth: key as username, secret as password.
Is it safe to let Claude near my PMS?
The server here is read-only and runs on your own machine over stdio, with credentials in environment variables. Claude can query data but cannot change anything. The Server key itself is powerful (it reads guest and owner data), so guard it the way you'd guard an admin password.
Can I do this with Guesty, Hostaway, or OwnerRez too?
Yes, the structure is identical for any PMS with a public REST API; only the auth step changes. See the rest of the series at Claude guides for your STR stack, including Guesty, Hostaway, and OwnerRez.
Sources
- Track API developer hubhttps://developer.trackhs.com/
- Track API reference: Unit search (GET /api/pms/units)https://developer.trackhs.com/reference/getunits
- Track API reference: Search reservations (GET /api/pms/reservations)https://developer.trackhs.com/reference/getreservations
- Airbyte: Track PMS connector (auth scheme and stream list)https://docs.airbyte.com/integrations/sources/track-pms
- Operto Teams: TrackHS connection instructions (Server key path)https://teams.operto.com/trackhs/instructions
- Track (TravelNet Solutions): product sitehttps://trackhospitality.com/
- Model Context Protocol: Build an MCP serverhttps://modelcontextprotocol.io/quickstart/server
- Claude Code: Connect to tools via MCPhttps://code.claude.com/docs/en/mcp
Independent integration guide. RapidEye is not affiliated with or endorsed by Track, TravelNet Solutions, or Anthropic. API behavior and dashboard menus can change; confirm specifics against the official docs above. Last reviewed July 18, 2026.

