Developers
Your data. Your API. Included free.
Every WaterMark plan includes a read API, incremental export feeds, and signed webhooks — no setup fee, monthly add-on, or per-call charge. Pull customers, visits, invoices, and payment-ledger rows into the tools you run; use a deliberately scoped connector for two safe office actions.
Quickstart
Two minutes from zero to your customer list.
- 1. Mint a key. In the office dashboard, open Settings → API keys (owner accounts only), name it (e.g.
"Zapier"), and create. The full key —wk_…— is shown exactly once; we store only its hash, so copy it right away. - 2. Call the API. Send it as a Bearer token:
curl https://watermarkapp.online/api/v1/customers \
-H "Authorization: Bearer wk_YOUR_KEY"
# Python
import requests
r = requests.get("https://watermarkapp.online/api/v1/customers",
headers={"Authorization": "Bearer wk_YOUR_KEY"}, timeout=15)
print(r.json()["data"])
// Node
const r = await fetch("https://watermarkapp.online/api/v1/customers", {
headers: { Authorization: "Bearer wk_YOUR_KEY" },
});
console.log((await r.json()).data);
Authentication & limits
- Header:
Authorization: Bearer wk_…on every request. A missing, malformed, or revoked key returns401. - Rate limit: 120 requests per minute per key. Over budget returns
429— back off and retry. - Scope: every key starts with
readonly and sees only your company's data. An owner may explicitly add theautomationscope for the two documented Zapier actions; it does not grant invoice, job, payment, or customer creation. - Key hygiene: secrets are stored as SHA-256 hashes (never retrievable), every key shows its last-used time in the dashboard, and revocation is instant and permanent. Rotate by minting a new key and revoking the old one.
Short-lived client credentials
Low-code and server integrations may exchange the displayed key prefix (client ID) plus the one-time key (client secret) for a short-lived access token (15 minutes by default — the response's expires_in is authoritative). Revoking the backing key invalidates that token immediately.
curl -u "wk_PREFIX:wk_FULL_KEY" \
-d "grant_type=client_credentials&scope=read" \
https://watermarkapp.online/api/v1/oauth/token
The response contains access_token, token_type: "Bearer", expires_in, and the granted scope. API access tokens are purpose-bound and cannot sign in to the WaterMark office.
Pagination
Every list endpoint takes ?page= (1-based) and ?per_page= (default 50, max 200) and returns the same envelope:
{
"object": "list",
"page": 1,
"per_page": 50,
"total": 214,
"has_more": true,
"data": [ … ]
}
Endpoints
Your customer list, oldest first.
{
"id": 42,
"name": "Rivera, Dana",
"address": "18 Lakeview Dr, Denville, NJ 07834",
"phone": "9735550142",
"email": "dana@example.com",
"state": "NJ",
"tags": ["weekly", "gate-code"],
"credit_balance": 0.0
}
Service visits, newest first.
{
"id": 9310,
"pool_id": 57,
"customer_id": 42,
"technician_id": 3,
"status": "complete",
"scheduled_date": "2026-07-10",
"completed_at": "2026-07-10T18:22:41+00:00"
}
Invoices, newest first — amounts, payments, balance, and tax, straight from the ledger.
{
"id": 5120,
"customer_id": 42,
"job_id": 9310,
"amount": 145.00,
"amount_paid": 145.00,
"balance": 0.0,
"tax": 9.02,
"status": "paid",
"description": "Weekly service — July",
"created_at": "2026-07-10T18:23:05+00:00",
"paid_at": "2026-07-11T14:02:33+00:00"
}
Immutable money-in ledger rows. Processor identifiers and internal reconciliation notes are deliberately omitted.
{
"id": 8122,
"customer_id": 42,
"invoice_id": 5120,
"amount": 145.00,
"method": "card",
"created_at": "2026-07-11T14:02:33Z",
"modified_on": "2026-07-11T14:02:33Z"
}
Incremental export feeds
Use customers, jobs, invoices, or payments. Start with an optional ?modified_after=<ISO-8601>&limit=100, follow next_cursor while has_more is true, then persist the final checkpoint for your next poll.
- A signed high-water mark freezes each page walk, so records changed during a long export are picked up on the next poll instead of skipped.
limitdefaults to 100 and is capped at 500. Cursors are bound to one company and one resource.- Current-state feed: it returns records that still exist and does not emit deletion tombstones. Run a periodic full reconciliation if your destination must detect hard-deleted records.
Webhooks
Push, not poll. Register an HTTPS endpoint in the dashboard and WaterMark POSTs you a signed JSON envelope the moment things happen — the exact pattern Zapier's and Make's webhook triggers consume directly.
Events
| Event | Fires when… |
|---|---|
customer.created | a new customer is added |
job.completed | a tech completes a visit |
job.rescheduled | a visit moves to a new date |
job.skipped | a visit is skipped (with the reason) |
invoice.paid | an invoice is fully paid |
payment.received | money lands on an invoice |
lead.created | a booking-page lead arrives |
estimate.approved | a customer approves a quote |
review.received | a customer leaves a review |
agreement.signed | a service agreement is signed |
Delivery & verification
- Each delivery carries
X-WaterMark-Event(the event name) andX-WaterMark-Signature— an HMAC-SHA256 hex digest of the raw request body using your webhook's secret. Verify it before trusting the payload. - The JSON body is an envelope with the event's fields nested under
data:{"event": "...", "sent_at": "<ISO timestamp>", "org_id": 123, "data": {…event fields…}}. Readbody.data.invoice_id, notbody.invoice_id. - At-least-once: failed deliveries are persisted and retried on a backoff ladder (five attempts over roughly four days), and the office can replay any delivery manually. Retries of the same delivery carry
X-WaterMark-Delivery-Id— dedupe on it. - Respond with any
2xxquickly; do your processing async.
# Python — verify the signature
import hmac, hashlib
def verify(secret: str, raw_body: bytes, signature: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")
// Node — verify the signature
const crypto = require("node:crypto");
function verify(secret, rawBody, signature) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ""));
}
Zapier / Make triggers in 60 seconds
- In Zapier, add a Webhooks by Zapier → Catch Hook trigger (in Make, a Custom webhook) and copy its URL.
- In WaterMark, register that URL as a webhook and pick the events you want.
- Done —
job.completedcan append to a Sheet,invoice.paidcan post to Slack,lead.createdcan drop into your CRM.
A connector can register the same trigger rail through POST /api/v1/zapier/subscriptions and stop only its own subscription through DELETE /api/v1/zapier/subscriptions/{id}. Both require an API key with the explicit automation scope and an Idempotency-Key header.
Minimal Zapier actions
These are the only public writes. Each requires the automation scope plus a stable Idempotency-Key; replaying the same key returns the first result without a second effect.
Create a visible office follow-up task, optionally linked to an in-company customer, job, invoice, or business unit.
{
"title": "Follow up on heater quote",
"priority": "high",
"category": "follow_up",
"customer_id": 42
}
Append a general, service, or billing note. Automation keys cannot write gate codes or access instructions.
{
"customer_id": 42,
"body": "Customer requested a Friday callback",
"kind": "general"
}
Use GET /api/v1/zapier/catalog to discover the live trigger, action, and starter-template contract instead of hard-coding marketing copy.
What it costs
Nothing. The API and webhooks are included on every WaterMark plan — no activation fee, no monthly API add-on, no per-call metering. Many pool platforms gate API access behind higher tiers or charge for it (verify each vendor's current pricing). We think your data is yours, full stop.