The REST API lets your own systems read and create contacts, notes and deals, read pipelines and store orders, send daily ad spend, and subscribe to webhooks. This page covers the rules shared by every endpoint. The endpoints themselves are described in Contacts, Pipelines and deals, Ad spend and Webhooks.
Before you start
You need an API key. Owners and administrators create keys under Contacts, then Tools, then API keys, when the plan includes API access. See API keys.
Base URL
Every endpoint lives under one base URL:
https://rutbacrm.com/api/v1
All paths in this guide are relative to it, so GET /contacts means GET https://rutbacrm.com/api/v1/contacts.
HTTPS is required. A request over plain HTTP is refused with 403 HTTPS is required. rather than redirected, because by then the key has already crossed the network unprotected. If that happens, revoke the key and create a new one.
Authentication
Send the key as a bearer token in the Authorization header of every request:
curl https://rutbacrm.com/api/v1/me \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
The response names the company the key belongs to, which makes it a good first test:
{
"organization": {
"id": "ORGANIZATION_ID",
"name": "Your company"
}
}
A missing, wrong or revoked key gets the same answer, 401 with {"message": "Invalid API key."}, so the response never reveals whether a key exists.
Requests and responses
| Rule | Detail |
|---|---|
| Format | JSON only. POST and DELETE requests must send Content-Type: application/json, or they are refused with 415 Send JSON., even when there is no body |
| Accept | Send Accept: application/json |
| IDs | Every ID is a UUID string, for example 9d3c1f6e-2b7a-4c55-9a1e-3f0b8e2d7c41. A path ID that is not a UUID returns 404 |
| Dates | Dates you send are YYYY-MM-DD. Times you receive are ISO 8601 in UTC |
| Money | Amounts you send are strings such as "1250.50", with at most two decimals. A JSON number is refused for deal amounts |
| Currencies | USD, EUR, GBP, TRY, SAR and AED |
| Caching | Every response carries Cache-Control: no-store, private |
| Browsers | There are no CORS headers. Call the API from a server, never from a web page |
Successful responses wrap the result in data, except GET /me and the webhook endpoints, which return their fields at the top level.
Endpoints
| Method | Path | Key access | Role | Purpose |
|---|---|---|---|---|
| GET | /me |
Read | Any | The company the key belongs to |
| GET | /contacts |
Read | Any | List and search contacts |
| GET | /contacts/CONTACT_ID |
Read | Any | One contact with tags and custom fields |
| GET | /contacts/CONTACT_ID/orders |
Read | Any | A contact's store orders |
| POST | /contacts |
Write | Member or above | Create a contact |
| POST | /contacts/CONTACT_ID/notes |
Write | Member or above | Add a note |
| GET | /pipelines |
Read | Any | Pipelines and their stages |
| POST | /deals |
Write | Member or above | Open a deal |
| POST | /ad-spend |
Write | Owner or administrator | Send daily ad spend |
| GET | /ad-conversions |
Read | Owner or administrator | Sales from Google clicks waiting for upload |
| POST | /ad-conversions/confirm |
Write | Owner or administrator | Mark uploaded sales as sent |
| POST | /hooks |
Write | Owner or administrator | Subscribe a webhook |
| DELETE | /hooks/HOOK_ID |
Write | Owner or administrator | Remove a webhook |
"Role" is the role of the person who created the key. Viewers can only read.
There is also GET https://rutbacrm.com/api/v1/organizations/ORGANIZATION_ID/context, which the app itself uses with a signed-in browser session. It does not accept API keys and is not meant for integrations.
Pagination
Only GET /contacts is paginated. It returns 25 contacts per page:
{
"data": [],
"page": 1,
"pages": 4,
"total": 87
}
Ask for the next page with ?page=2. Pages run from 1 to 99999. A page past the end returns an empty data list, so stop when page equals pages. The other lists are short and complete: up to 20 pipelines of up to 12 stages each, and the 10 newest orders of a contact.
Searching and filtering
GET /contacts takes q, a search of up to 120 characters that matches words in the name, company name, email and phone. Results are newest-updated first. Archived, merged and erased contacts are left out of the list. There are no other filters: status, owner and tag filters exist in the app but not in the API.
Rate limits
| Limit | Scope | When exceeded |
|---|---|---|
| 120 requests per minute | Each key, across all endpoints. The count starts again at the start of each clock minute | 429 with {"message": "Too many requests."} |
| 600 requests per minute | Each sending address, per endpoint | 429 with {"message": "Too Many Attempts."} and a Retry-After header |
The X-RateLimit-Limit and X-RateLimit-Remaining headers on each response describe the per-address limit, not the per-key one. Keep your own count to stay under 120 a minute per key, or spread work across separate keys for separate systems. Refused requests that fail the key, plan or JSON checks are not counted against the key.
Errors
Errors are JSON with a message, and validation errors add an errors object keyed by field. The full list of status codes and their causes is in Errors and limits.
Idempotency
There is no idempotency header. Plan retries with this in mind:
POST /contactsprotects you from duplicates: a second contact with the same email or phone returns409unless you confirm it.POST /contacts/CONTACT_ID/notesandPOST /dealscreate a new record on every call. If a request times out, check before you send it again.POST /ad-spendis safe to repeat: sending the same day again replaces or keeps the earlier figure rather than adding to it.POST /ad-conversions/confirmis safe to repeat: a sale already confirmed is not counted again, and the answer says how many were newly confirmed.
Versioning
The current and only version is v1, and it is part of the base URL. There is no version header.
What the API does not do
The API cannot update or delete contacts or deals, move deals between stages, create tasks, or send messages. There is no OAuth and no OpenAPI file. To react to events in the app, use webhooks instead of polling.
Last updated 2026-11-08