API Overview
Welcome to the Pooli API, a complementary extension to our intuitive Pooli app interface. While the app provides a user-friendly way to manage your pool, our API opens up additional possibilities, enabling advanced interactions with your pool data programmatically. This powerful combination means that you can now automate routine tasks, integrate with other systems, or even create custom applications, all while continuing to enjoy the ease and familiarity of the Pooli app. The Pooli API serves as your versatile gateway to comprehensive and efficient pool management.
Version: 1.0 (Alpha) - Date: August 4, 2025
Note: This is a beta version of the Pooli API and may be subject to changes and updates.
OpenAPI specification
The machine-readable definition is an OpenAPI 3 spec.
- Spec: https://api.pooli.app/openapi.json.
https://api.pooli.app/v1/openapi.jsonserves the same document. - Development spec: https://dev-api.pooli.app/openapi.json
The development host is https://dev-api.pooli.app. api-dev.pooli.app does not resolve, so do not use it.
The spec documents every route, including the legacy alias paths. This page is the full guide.
Key Features
- Programmatic Pool Management: Seamlessly create and delete pools using our API. This feature allows for dynamic management of pool data, offering flexibility and control to automate your pool maintenance and management tasks.
- Reading Inventory: Get real-time access to your pool inventory. View available supplies, equipment status, and reorder levels with simple API calls.
- Managing To-Dos: Stay on top of your pool maintenance tasks. The API provides functionalities to manage and update your to-do lists, helping you prioritize and schedule maintenance activities efficiently.
- Adding Logs: Easily add detailed logs to track pool maintenance, chemical levels, and more. Our API allows for quick log entries, ensuring you have up-to-date information about your pool's condition.
- Accessing All Logs: Retrieve comprehensive log data for any specified time range. This feature is ideal for analyzing trends, monitoring pool health over time, and making informed decisions about pool care.
With the Pooli API, you can create custom scripts and applications that leverage these features, streamlining your pool management process and enhancing your pool experience. Our robust, easy-to-use API is designed to empower users, developers, and pool enthusiasts alike to innovate and optimize pool care.
For feature requests, bug reports, or any questions, please email support@pooli.app.
Rate Limiting and Quotas
At Pooli, we have implemented rate limiting to ensure fair and efficient usage of our API. The rate limits vary based on the user's membership status in the Pool Club.
Pool Club Members
Pool Club members enjoy the benefit of unlimited API requests. This privilege allows for more extensive use of the API, facilitating advanced automation, integration, and application development without the concern of hitting usage caps. Discover the huge list of features and benefits of being a Pool Club member and consider signing up to enjoy these advantages.
Non-Club Members
Non-club members are subject to a rate limit of 60 requests per hour. This limit ensures that all users have fair access to the API while preventing excessive use that could impact service quality.
It is important for all users to be aware of these limits to avoid interruptions in service. If you encounter any issues or have questions about our rate limiting policies, please contact support@pooli.app.
Authentication
The Pooli API authenticates every request with a Bearer credential. A long-lived API key is a valid Bearer credential. An access token from POST /v1/auth/token is also a valid Bearer credential. Each key belongs to one user.
Step 1: Generate Your API Key
Create your API key in the Pooli app. The key belongs to the user who created it and provides access to that user's documents, plus any organization where that user is an owner, an admin, or an active technician.
Rate Limiting
Rate limiting is applied on a per-user basis to ensure fair usage and system stability. Be mindful of your request frequency to stay within the rate limits. See Rate Limiting and Quotas.
API Base URLs
The Pooli API is available at the following endpoints:
- Production:
https://api.pooli.app - Development:
https://dev-api.pooli.app
Use dev-api.pooli.app for development. api-dev.pooli.app does not resolve.
Canonical user paths are /v1/users/{uid}/.... Canonical organization paths are /v1/organizations/{orgId}/.... The legacy aliases /users/{uid}/... and /organizations/{orgId}/... stay supported and are listed in the OpenAPI spec. New integrations should use the /v1 paths.
Using the API Key
Send the long-lived API key on every request that should run as that key:
Authorization: Bearer YOUR_API_KEY
Personal routes under /v1/users are for the key's own user only. The user id in the path must be the user id on the key.
API keys do not expire. A key's expiresAt field is reserved for a future maximum lifetime and is not enforced today. A key stops working only when you revoke it, or when a rotation grace period ends. Access tokens are different: each one has its own expiresAt, 15 minutes by default.
Versioning
API versions are specified in the url. The current version is v1. For example, to fetch all pools for a user:
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/users/YOUR_USER_ID/pools'
To fetch a specific pool:
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/users/YOUR_USER_ID/pools/YOUR_POOL_ID'
Replace YOUR_API_KEY, YOUR_USER_ID, and YOUR_POOL_ID with your API key, user ID, and pool ID. An access token can stand in for the API key in the same Authorization header.
Access tokens
Endpoint: POST /v1/auth/token
Description: Exchanges a long-lived API key for an access token. The token lasts 15 minutes. You can request a longer lifetime, up to 60 minutes. Send the access token as Authorization: Bearer YOUR_ACCESS_TOKEN.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/auth/token'
Endpoint: POST /v1/auth/token/refresh
Description: Issues a new access token from a current access token. The new token follows the same lifetime: 15 minutes, with a maximum of 60 minutes.
curl -X POST \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/auth/token/refresh'
API key lifecycle
These routes manage the caller's API key. Authenticate with that key, or with an access token issued for it.
Regenerate
Endpoint: POST /v1/auth/keys/regenerate
Description: Invalidates outstanding access tokens and keeps the same API key string.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/auth/keys/regenerate'
Rotate
Endpoint: POST /v1/auth/keys/rotate
Description: Issues a new API key. The previous key keeps working until graceEndsAt. The grace period defaults to 7 days. Set it from 0 to 90 days with gracePeriodDays. The response includes graceEndsAt. After graceEndsAt, the next request with the old key revokes it and its access tokens, and that request returns 403.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"gracePeriodDays":7}' \
'https://api.pooli.app/v1/auth/keys/rotate'
Revoke
Endpoint: POST /v1/auth/keys/revoke
Description: Revokes the API key and its access tokens. Revocation takes effect immediately, including during a rotation grace period.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/auth/keys/revoke'
Acting as another user
On organization routes, the organization owner can send X-Pooli-Act-As set to the user id to act as. The header is owner-only. It is rejected on /v1/users routes.
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Pooli-Act-As: USER_ID" \
-H "Content-Type: application/json" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools'
Idempotency-Key
Idempotency-Key is required on creates for customers, organization pools, stops, inventory, and reminders. The key must be 8 to 200 characters. Send a new key for each new create. Send the same key again when you retry that create. The same key with the same body replays the first response. The same key with a different body returns 409.
Idempotency-Key: 6f1c2b4e-9a7d-4c31-8e2a-1b5d0f3a7c88
Roles
Personal routes under /v1/users are for the key's own user only.
Organization routes accept an organization owner, an organization admin, or an active technician, within these limits:
- Owner and admin: billing (invoices and payments), estimates, service subscriptions, customer create, organization pool create, patch, and delete, CSV import, and technician status.
- Owner, admin, and active technicians: creating stops, and creating, patching, and deleting organization logs.
An active technician is a user in the organization's technicians collection with status active. Technician status values are active, inactive, and rejected. Sending approved returns 400.
Organization access
There is no separate organization key. A personal API key works on /v1/organizations/{orgId}/... routes when its user is the organization owner, an admin, or an active technician.
- Read-only resources: customers, routes, stops, invoices, estimates, subscriptions, payments, products, equipment, service visits, and service reports. These routes do not create or update records.
- Billing reads: invoices, payments, estimates, service subscriptions, and billing fields on customers are limited to owners and admins. Technicians still receive customer name, phone, email, address, notes, and pools.
- Card data: responses never include card numbers, CVC, expiry, or last4.
- Writes: no API write calls Stripe or accepts billing fields. Stops store the pool object the app embeds. Reminder create uses the same reminder scheduler as the app. Personal
/v1/userswrites (pools, logs, inventory, and reminders) require the path uid to match the key user and rejectX-Pooli-Act-As.
Route dates: routes use the organization's timeZone when it is an IANA name such as America/Los_Angeles, or timeZoneOffset in minutes (local minus UTC, so PDT is -420). If the organization has neither, dates use UTC, and route responses show timeZoneSource.
See Organization Endpoints for each route.
Errors
Errors are JSON with the HTTP status repeated in the body. Some errors add a details field.
{ "error": "Invalid idempotency key", "message": "Idempotency-Key must be between 8 and 200 characters", "status": 400 }
Pagination
Every list accepts limit (positive integer, maximum 200), cursor, startAfter (an alias of cursor), and envelope=1. By default the body is a JSON array. An empty log page and an empty inventory return null instead of an empty array. envelope=1 returns { data, page }, where page has limit, nextCursor, and hasMore, and data is always an array. Every list also sets the X-Next-Cursor and X-Has-More headers.
Legacy field spellings
supplimentalSanitizers is the intentional field name. cartrige is the intentional filter spelling used by the app, CSV import, and the value stored when filterCategory is omitted on create. Do not correct either spelling in requests.
Endpoint reference
Canonical paths use the /v1 prefix. /users/{uid} is a legacy alias of /v1/users/{uid}, and /organizations/{orgId} is a legacy alias of /v1/organizations/{orgId}. Those aliases stay supported. Each route below is listed once, in the /v1 form.
List endpoints accept limit (positive integer, maximum 200), cursor, startAfter, and envelope. envelope=1 returns { data, page }. Omit envelope to keep the JSON array. Send Authorization: Bearer with an API key or an access token. See Authentication, Idempotency-Key, Act as, and Roles.
Read only. Invoices, payments, estimates, and service subscriptions are read only. There are no invoice, payment, refund, or Stripe writes.
User endpoints
Personal routes under /v1/users are for the key's own user only.
Pools
Personal pools for the key user. The user id in the path must be the user id on the key.
GET /v1/users/{uid}/pools
Access: Key user only
Idempotency-Key: Not required
List pools for a user.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools'
# 200
[{"id":"POOL_ID","name":"Main pool"}]
POST /v1/users/{uid}/pools
Access: Key user only
Idempotency-Key: Not required
Create a user pool.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pool":{"name":"Main pool","gallonEstimate":20000}}' \
'https://api.pooli.app/v1/users/USER_ID/pools'
# 200
{"id":"POOL_ID","name":"Main pool"}
GET /v1/users/{uid}/pools/{poolId}
Access: Key user only
Idempotency-Key: Not required
Get one user pool.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID'
# 200
{"id":"POOL_ID","name":"Main pool"}
PATCH /v1/users/{uid}/pools/{poolId}
Access: Key user only
Idempotency-Key: Not required
Update a user pool.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Main pool"}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID'
# 200
{"id":"POOL_ID","name":"Main pool"}
DELETE /v1/users/{uid}/pools/{poolId}
Access: Key user only
Idempotency-Key: Not required
Soft-delete a user pool.
Example
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID'
# 200
{"success":true}
Logs
Personal pool logs for the key user. Create requires the type query. Implemented types: test, product, maintenance, note, water, temperature, pressure, swg, feeder, swim. For type=maintenance, load task ids from GET /v1/users/{uid}/maintenance-catalog and copy id, name, category, and duration into each maintenances item.
A swim create sends numSwimmers (positive integer), duration (positive integer seconds), and an optional note. The server stores swimTime as numSwimmers times duration and ignores a client swimTime. Invalid values return 400 with error "Validation Error".
GET /v1/users/{uid}/pools/{poolId}/logs
Access: Key user only
Idempotency-Key: Not required
List logs for a user pool.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs'
# 200
[{"id":"LOG_ID","type":"test"}]
POST /v1/users/{uid}/pools/{poolId}/logs
Access: Key user only
Idempotency-Key: Not required
Create a user pool log.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"chemicalReading":{"ph":7.2}}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs?type=test'
# 201
{"success":true,"message":"created","data":"LOG_ID"}
Example: swim
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"numSwimmers":2,"duration":1800,"note":"Morning laps","creatorName":"Alex"}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs?type=swim'
# 201
{"success":true,"message":"Log created successfully","data":"6f1c0c2e-4b7a-4e1d-9c3a-8b2d5e7f1a90"}
GET /v1/users/{uid}/pools/{poolId}/logs/{logId}
Access: Key user only
Idempotency-Key: Not required
Get one user pool log.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"id":"LOG_ID","type":"test"}
PATCH /v1/users/{uid}/pools/{poolId}/logs/{logId}
Access: Key user only
Idempotency-Key: Not required
Update a user pool log.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"Brushed walls"}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"id":"LOG_ID","type":"test"}
DELETE /v1/users/{uid}/pools/{poolId}/logs/{logId}
Access: Key user only
Idempotency-Key: Not required
Delete a user pool log.
Example
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"success":true,"message":"ok"}
Inventory
Personal inventory for the key user. Create requires Idempotency-Key.
GET /v1/users/{uid}/inventory
Access: Key user only
Idempotency-Key: Not required
List inventory for a user.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/inventory'
# 200
[{"id":"ITEM_ID"}]
POST /v1/users/{uid}/inventory
Access: Key user only
Idempotency-Key: Required
Create a user inventory item.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"name":"Chlorine tabs","quantity":1}' \
'https://api.pooli.app/v1/users/USER_ID/inventory'
# 201
{"id":"ITEM_ID"}
PATCH /v1/users/{uid}/inventory/{itemId}
Access: Key user only
Idempotency-Key: Not required
Update a user inventory item.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"quantity":2}' \
'https://api.pooli.app/v1/users/USER_ID/inventory/ITEM_ID'
# 200
{"id":"ITEM_ID"}
Reminders
Personal reminders for the key user. Create requires reminderType and Idempotency-Key.
GET /v1/users/{uid}/pools/{poolId}/reminders
Access: Key user only
Idempotency-Key: Not required
List reminders for a user pool.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/reminders'
# 200
[{"id":"REMINDER_ID","poolId":"POOL_ID"}]
POST /v1/users/{uid}/pools/{poolId}/reminders
Access: Key user only
Idempotency-Key: Required
Create a user pool reminder.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"reminderType":"test","message":"Test the water"}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/reminders'
# 201
{"id":"REMINDER_ID","poolId":"POOL_ID"}
PATCH /v1/users/{uid}/pools/{poolId}/reminders/{reminderId}
Access: Key user only
Idempotency-Key: Not required
Update a user pool reminder.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message":"Test the water"}' \
'https://api.pooli.app/v1/users/USER_ID/pools/POOL_ID/reminders/REMINDER_ID'
# 200
{"id":"REMINDER_ID","poolId":"POOL_ID"}
Maintenance catalog
Built-in maintenance tasks plus this user's custom tasks (users/{uid}.prefs.customMaintenance). Hidden, disabled, deleted, isDeleted rows, blank names, and custom rows whose category is not custom are omitted. Silencing a task is stored on the pool and does not remove it. aliases is present only when the stored task already has that array. duration is seconds.
GET /v1/users/{uid}/maintenance-catalog
Access: Key user only
Idempotency-Key: Not required
List the maintenance catalog. Copy id, name, category, duration, and aggregationName (when present) into each maintenances item on POST /v1/users/{uid}/pools/{poolId}/logs?type=maintenance. group and custom are ignored on create. Supports limit, cursor, startAfter, and envelope=1. Legacy alias GET /users/{uid}/maintenance-catalog.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/users/USER_ID/maintenance-catalog'
# 200
[{"id":"cleanSkimmers","name":"Empty Skimmer Basket(s)","category":"emptySkimmers","group":"Weekly","custom":false,"duration":345600},{"id":"sweepPool","name":"Brush Walls","category":"sweepWalls","group":"Weekly","custom":false,"duration":1814400},{"id":"oasis-walls","name":"Brush the oasis walls","category":"custom","group":"Weekly","custom":true,"duration":604800}]
Organization endpoints
These routes require an organization owner, an admin, or an active technician, within the limits in Roles. The organization owner can send X-Pooli-Act-As on these routes. See Act as.
Technician management
Listing is available to an owner, an admin, or an active technician. If the status query is present, only pending technicians are returned and the value is ignored. Setting status is owner or admin. Write values are active, inactive, or rejected.
GET /v1/organizations/{orgId}/technicians
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Lists technicians. If status is present, only pending technicians are returned and the value is ignored.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/technicians'
# 200
[{"id":"TECH_ID","status":"active"}]
POST /v1/organizations/{orgId}/technicians/{techId}/status
Access: Owner or admin
Idempotency-Key: Not required
Set a technician status.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"active"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/technicians/TECH_ID/status'
# 200
{"success":true}
Pool management
List and get are available to an owner, an admin, or an active technician. Create, update, and delete are owner or admin. Create requires Idempotency-Key and returns HTTP 200. The assigned query is compared to isAssigned as a string.
GET /v1/organizations/{orgId}/pools
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List organization pools.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools'
# 200
[{"id":"POOL_ID","name":"Main pool"}]
POST /v1/organizations/{orgId}/pools
Access: Owner or admin
Idempotency-Key: Required
Create an organization pool.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"pool":{"name":"Main pool","gallonEstimate":20000}}' \
'https://api.pooli.app/v1/organizations/ORG_ID/pools'
# 200
{"id":"POOL_ID","name":"Main pool"}
GET /v1/organizations/{orgId}/pools/{poolId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one organization pool.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID'
# 200
{"id":"POOL_ID","name":"Main pool"}
PATCH /v1/organizations/{orgId}/pools/{poolId}
Access: Owner or admin
Idempotency-Key: Not required
Update an organization pool.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Main pool"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID'
# 200
{"id":"POOL_ID","name":"Main pool"}
DELETE /v1/organizations/{orgId}/pools/{poolId}
Access: Owner or admin
Idempotency-Key: Not required
Soft-delete an organization pool.
Example
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID'
# 200
{"success":true}
Log management
List and get are available to an owner, an admin, or an active technician. Create, update, and delete are too. Create requires the type query. For type=maintenance, load task ids from GET /v1/organizations/{orgId}/maintenance-catalog and copy id, name, category, and duration into each maintenances item.
Implemented types include swim. The swim body matches the user route: numSwimmers, duration in seconds, and an optional note. The stored userId is the key user.
GET /v1/organizations/{orgId}/pools/{poolId}/logs
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List logs for an organization pool.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs'
# 200
[{"id":"LOG_ID","type":"test"}]
POST /v1/organizations/{orgId}/pools/{poolId}/logs
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Create an organization pool log.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"chemicalReading":{"ph":7.2}}' \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs?type=test'
# 201
{"success":true,"message":"created","data":"LOG_ID"}
Example: swim
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"numSwimmers":2,"duration":1800,"note":"Morning laps","creatorName":"Alex"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs?type=swim'
# 201
{"success":true,"message":"Log created successfully","data":"6f1c0c2e-4b7a-4e1d-9c3a-8b2d5e7f1a90"}
GET /v1/organizations/{orgId}/pools/{poolId}/logs/{logId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one organization pool log.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"id":"LOG_ID","type":"test"}
PATCH /v1/organizations/{orgId}/pools/{poolId}/logs/{logId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Update an organization pool log.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"Brushed walls"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"id":"LOG_ID","type":"test"}
DELETE /v1/organizations/{orgId}/pools/{poolId}/logs/{logId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Delete an organization pool log.
Example
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/pools/POOL_ID/logs/LOG_ID'
# 200
{"success":true,"message":"ok"}
Maintenance catalog
Owner, admin, or active technician. Same catalog. Built-in defaults shorter than 7 days are raised to 7 days (matches the pro app). Custom tasks come from the effective user's users/{uid}.prefs.customMaintenance (API key user, or the X-Pooli-Act-As user when the owner acts as someone else). There is no organization collection.
GET /v1/organizations/{orgId}/maintenance-catalog
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List the maintenance catalog. Supports limit, cursor, startAfter, and envelope=1. Legacy alias GET /organizations/{orgId}/maintenance-catalog.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/maintenance-catalog'
# 200
[{"id":"cleanSkimmers","name":"Empty Skimmer Basket(s)","category":"emptySkimmers","group":"Weekly","custom":false,"duration":604800},{"id":"sweepPool","name":"Brush Walls","category":"sweepWalls","group":"Weekly","custom":false,"duration":1814400},{"id":"oasis-filter","name":"Clean the filter at the oasis","category":"custom","group":"Monthly","custom":true,"duration":2592000}]
Customers
List and get are read only. An active technician receives service fields (name, phone, email, address, notes, and pools). Billing fields are omitted for an active technician. Create and CSV import are owner or admin. Create requires Idempotency-Key and rejects billing fields and payment methods.
GET /v1/organizations/{orgId}/customers
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List organization customers. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/customers'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
POST /v1/organizations/{orgId}/customers
Access: Owner or admin
Idempotency-Key: Required
Create an organization customer.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"name":"Sample Customer"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/customers'
# 201
{"id":"RECORD_ID","name":"Sample"}
GET /v1/organizations/{orgId}/customers/{customerId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one organization customer. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/customers/CUSTOMER_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
POST /v1/organizations/{orgId}/import/csv
Access: Owner or admin
Idempotency-Key: Not required
Import customers and pools from CSV.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"csvText":"name,pool_name\nSample Customer,Main pool"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/import/csv'
# 200
{"jobId":"JOB_ID","counts":{"created":1}}
Routes
Read only. A route is the active stops for one technician on one local calendar date, derived from stops. Dates use the organization time zone, or UTC when the organization has no usable zone.
GET /v1/organizations/{orgId}/routes
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List routes. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/routes?date=2026-10-19'
# 200
[{"id":"2026-10-19:USER_ID","organizationId":"ORG_ID","date":"2026-10-19","stopIds":["STOP_ID"],"timeZoneSource":"utc-fallback"}]
GET /v1/organizations/{orgId}/routes/{routeId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one route. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/routes/2026-10-19:USER_ID'
# 200
{"id":"2026-10-19:USER_ID","organizationId":"ORG_ID","date":"2026-10-19","stopIds":["STOP_ID"],"timeZoneSource":"utc-fallback"}
Stops
List and get are read only. Create is available to an owner, an admin, or an active technician and requires Idempotency-Key. poolId and scheduledDateTime are required.
GET /v1/organizations/{orgId}/stops
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List route stops. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/stops'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
POST /v1/organizations/{orgId}/stops
Access: Owner, admin, or active technician
Idempotency-Key: Required
Create a route stop.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"poolId":"POOL_ID","scheduledDateTime":"2026-10-19T15:00:00.000Z"}' \
'https://api.pooli.app/v1/organizations/ORG_ID/stops'
# 201
{"id":"RECORD_ID","name":"Sample"}
GET /v1/organizations/{orgId}/stops/{stopId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one route stop. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/stops/STOP_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Billing
Read only. Invoices, payments, estimates, and service subscriptions are read only. There are no invoice, payment, refund, or Stripe writes.
Invoices
Read only. Owner or admin. An active technician receives 403.
GET /v1/organizations/{orgId}/invoices
Access: Owner or admin
Idempotency-Key: Not required
List invoices. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/invoices'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/invoices/{invoiceId}
Access: Owner or admin
Idempotency-Key: Not required
Get one invoice. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/invoices/INVOICE_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Payments
Read only. Owner or admin. An active technician receives 403. The payment id is parentType~parentId~paymentId.
GET /v1/organizations/{orgId}/payments
Access: Owner or admin
Idempotency-Key: Not required
List payments. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/payments?invoiceId=INVOICE_ID'
# 200
[{"id":"invoice~INVOICE_ID~PAY_ID","amount":120}]
GET /v1/organizations/{orgId}/payments/{paymentId}
Access: Owner or admin
Idempotency-Key: Not required
Get one payment. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/payments/invoice~INVOICE_ID~PAY_ID'
# 200
{"id":"invoice~INVOICE_ID~PAY_ID","amount":120}
Estimates
Read only. Owner or admin. An active technician receives 403.
Read only. Invoices, payments, estimates, and service subscriptions are read only. There are no invoice, payment, refund, or Stripe writes.
GET /v1/organizations/{orgId}/estimates
Access: Owner or admin
Idempotency-Key: Not required
List estimates. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/estimates'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/estimates/{estimateId}
Access: Owner or admin
Idempotency-Key: Not required
Get one estimate. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/estimates/ESTIMATE_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Service subscriptions
Read only. Owner or admin. An active technician receives 403. These are organization service billing subscriptions, not Pool Club subscriptions.
Read only. Invoices, payments, estimates, and service subscriptions are read only. There are no invoice, payment, refund, or Stripe writes.
GET /v1/organizations/{orgId}/subscriptions
Access: Owner or admin
Idempotency-Key: Not required
List service subscriptions. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/subscriptions'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/subscriptions/{subscriptionId}
Access: Owner or admin
Idempotency-Key: Not required
Get one service subscription. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/subscriptions/SUBSCRIPTION_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Products
Read only product catalog for an owner, an admin, or an active technician.
GET /v1/organizations/{orgId}/products
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List the organization product catalog. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/products'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/products/{productId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one catalog product. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/products/PRODUCT_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Equipment
Read only installed equipment for an owner, an admin, or an active technician.
GET /v1/organizations/{orgId}/equipment
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List installed equipment. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/equipment'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/equipment/{equipmentId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one installed equipment item. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/equipment/EQUIPMENT_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Service visits
Read only. Customer signatures are omitted. Available to an owner, an admin, or an active technician.
GET /v1/organizations/{orgId}/service-visits
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List service visits. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/service-visits'
# 200
[{"id":"RECORD_ID","name":"Sample"}]
GET /v1/organizations/{orgId}/service-visits/{visitId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one service visit. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/service-visits/VISIT_ID'
# 200
{"id":"RECORD_ID","name":"Sample"}
Service reports
Read only projection of service visits that are completed, sending a report, or already have report fields.
GET /v1/organizations/{orgId}/service-reports
Access: Owner, admin, or active technician
Idempotency-Key: Not required
List service reports. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/service-reports'
# 200
[{"id":"VISIT_ID","poolId":"POOL_ID","visitId":"VISIT_ID"}]
GET /v1/organizations/{orgId}/service-reports/{visitId}
Access: Owner, admin, or active technician
Idempotency-Key: Not required
Get one service report. Read only.
Example
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/organizations/ORG_ID/service-reports/VISIT_ID'
# 200
{"id":"VISIT_ID","poolId":"POOL_ID","visitId":"VISIT_ID"}
Other routes
Content ingest is not organization scoped. Token and key routes are listed here in canonical form. The notes under Authentication stay the guide for tokens, rotation, act-as, and roles.
Content ingest
Service route for the key user. If the key has a non-empty scopes array, it must include content_management. Keys with no scopes array are allowed. Act-as is rejected.
POST /v1/content-ingest
Access: Key user only. If scopes is non-empty, it must include content_management.
Idempotency-Key: Not required
Create a content ingest item.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}' \
'https://api.pooli.app/v1/content-ingest'
# 201
{"id":"ITEM_ID"}
PATCH /v1/content-ingest/{id}
Access: Key user only. If scopes is non-empty, it must include content_management.
Idempotency-Key: Not required
Patch a content ingest item.
Example
curl -X PATCH \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}' \
'https://api.pooli.app/v1/content-ingest/ITEM_ID'
# 200
{"id":"ITEM_ID"}
Token and key routes
Canonical paths for the access token and API key routes described under Authentication. Idempotency-Key is not used on these routes.
POST /v1/auth/token
Access: Key user only
Idempotency-Key: Not required
Exchange a long-lived API key for a short-lived access token.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expiresInSeconds":900}' \
'https://api.pooli.app/v1/auth/token'
# 201
{"accessToken":"ACCESS_TOKEN","tokenType":"Bearer","expiresAt":"2026-10-19T15:00:00.000Z","expiresInSeconds":900}
POST /v1/auth/token/refresh
Access: Key user only
Idempotency-Key: Not required
Refresh a short-lived access token.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"expiresInSeconds":900}' \
'https://api.pooli.app/v1/auth/token/refresh'
# 201
{"accessToken":"ACCESS_TOKEN","tokenType":"Bearer","expiresAt":"2026-10-19T15:00:00.000Z","expiresInSeconds":900}
POST /v1/auth/keys/regenerate
Access: Key user only
Idempotency-Key: Not required
Revoke short-lived tokens for this API key.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/auth/keys/regenerate'
# 200
{"generation":1,"tokensRevoked":1,"keyRotated":false}
POST /v1/auth/keys/rotate
Access: Key user only
Idempotency-Key: Not required
Issue a new API key and start the grace period on this key.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"gracePeriodDays":7}' \
'https://api.pooli.app/v1/auth/keys/rotate'
# 200
{"apiKey":"NEW_API_KEY","previousKeyId":"PREVIOUS_KEY_ID","gracePeriodDays":7,"graceEndsAt":"2026-10-19T15:00:00.000Z","status":"rotating"}
POST /v1/auth/keys/revoke
Access: Key user only
Idempotency-Key: Not required
Revoke an API key before its grace period ends.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
'https://api.pooli.app/v1/auth/keys/revoke'
# 200
{"apiKeyId":"API_KEY_ID","status":"revoked"}
Spec and docs
Public files. No API key is required.
GET /docs
Access: Public
Idempotency-Key: Not required
API documentation page.
Example
curl -X GET \
'https://api.pooli.app/docs'
# 200
# HTML documentation page
GET /openapi.json
Access: Public
Idempotency-Key: Not required
OpenAPI document.
Example
curl -X GET \
'https://api.pooli.app/openapi.json'
# 200
# OpenAPI JSON document
GET /v1/docs
Access: Public
Idempotency-Key: Not required
API documentation page (versioned alias).
Example
curl -X GET \
'https://api.pooli.app/v1/docs'
# 200
# HTML documentation page
GET /v1/openapi.json
Access: Public
Idempotency-Key: Not required
OpenAPI document (versioned alias).
Example
curl -X GET \
'https://api.pooli.app/v1/openapi.json'
# 200
# OpenAPI JSON document
Customers & Pools CSV Import
Bulk import customers and pools for an organization. Each row typically represents one pool; customer columns may repeat.
The REST route is POST /v1/organizations/{orgId}/import/csv. Access is organization owner or admin. Send JSON with csvText, or a raw CSV body. See Customers for the endpoint card.
Access: Organization owner or admin.
- Upload in the app (Customers/Pools screen → Import menu)
- Or upload on the web in your Organization dashboard ("Import Customers & Pools CSV")
- API: Firebase Callable
customers-importCsv(group prefix required)
Request payload:
{
organizationId: "ORG_ID",
csvText: "organizationId,customer_name,...\n...",
// Optional advanced mapping/defaults; see guide for details
mapping: { /* header alias overrides */ },
defaults: { /* e.g., organizationId if not in every row */ }
}
Example client call:
// Web / Firebase Functions client
const importFn = firebase.functions().httpsCallable('customers-importCsv');
await importFn({ organizationId: 'abc123', csvText });
See the CSV Import Guide for exact fields, header aliases, defaults, and full examples.
Products CSV Import
Bulk create and update organization products from a CSV. Designed for retailers to upload their catalog in one click.
- Available via app and web dashboard only (no public callable API)
- Web: use the Products section on the Organization dashboard ("Import Products CSV")
Required columns: organizationId and either product_id (for updates) or both name and category (for new products).
Optional columns: description, defaultPrice, isTaxable, type (oneTime|subscription), stripeProductId, stripePriceId, cost, msrp, notes, tags (comma/semicolon/pipe separated), useMsrpForMarkup, deliverable, activeInStore, photos (comma/semicolon/pipe separated URLs).
Example header + rows (showing advanced fields on second row):
organizationId,product_id,name,category,defaultPrice,isTaxable,type,deliverable,activeInStore,tags,cost,msrp,notes,useMsrpForMarkup,photos
abc123,,Chlorine Tabs,Chemicals,19.99,true,oneTime,true,true,chlorine;tabs,,,,,
abc123,P123,Monthly Service,Services,120.00,true,subscription,false,false,service;recurring,80.00,150.00,"Includes filter cleaning",true,https://example.com/img1.jpg|https://example.com/img2.jpg