Skip to content

Developers

REST API

The public REST API: base URL, bearer authentication, scopes, money handling, pagination and errors.

A REST API for driving your own account: create work requests from your website, read your schedule into another system, reconcile invoices against your books.

https://your-elevateteam-host/api/public/v1

Everything is JSON. Money is always integer cents plus an explicit currency. Never a float, never a formatted string: a float is the wrong type for money and the mistake compounds silently.

Authentication

curl https://your-elevateteam-host/api/public/v1/services \
  -H "Authorization: Bearer sp_…"

Keys are created in Settings → Developers and shown once. They are stored as a hash, so nobody, including us, can recover one afterwards.

The key decides the account: there is no id or slug to send, and no way to ask for someone else's data. The key also carries the permissions of whoever created it, and stops working the moment that person is removed from the business.

Scopes

| Scope | Allows | | --- | --- | | read | GET requests | | write | everything, including read |

A write request from a read key returns 403 insufficient_scope.

Conventions

Pagination. List endpoints take page and per_page and return a meta object with the total count. Do not infer the end of a collection from a short page; read meta.

Timestamps are ISO 8601 in UTC. Dates without a time are plain YYYY-MM-DD and are interpreted in the account's timezone.

Idempotency. POST requests accept an Idempotency-Key header. Replaying a key returns the original response rather than creating a second record. Use it for anything triggered by a webhook you do not control.

Errors are a consistent shape:

{
  "error": "insufficient_scope",
  "message": "This key is read-only.",
  "details": {}
}

Rate limits

Per key, returned on every response:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587
X-RateLimit-Reset: 1757808000

Exceeding it returns 429 with a Retry-After header. Back off on the header, not on a fixed sleep.

A worked example

Create a request from your own website form:

curl -X POST https://your-elevateteam-host/api/public/v1/requests \
  -H "Authorization: Bearer sp_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: form-7c1f9a" \
  -d '{
    "contact": { "name": "Marla Okonjo", "email": "[email protected]" },
    "property": { "address": "14 Alder St", "city": "Portland", "region": "OR" },
    "summary": "Rooftop unit short-cycling, east wing",
    "preferred_window": "2026-09-17T18:00:00Z"
  }'

The response carries the created request and, when the contact matched an existing client, the client it was attached to.

Related

  • MCP server: the same account, addressed by an AI client
  • Webhooks: being told instead of polling