API Reference
Base URL: https://relmcrm.com
Authentication
All requests require an API key. Live keys start with sk_live_; test keys with sk_test_. Create both on the API Keys page.
Authorization: Bearer sk_live_...
Idempotency
Send an Idempotency-Key header (any string up to 255 chars) on any POST. Retries with the same key + same body return the original response; a different body returns 409 idempotency_conflict. Keys are scoped per workspace and stored for 24 hours.
curl -X POST $BASE/v1/contacts \
-H "Authorization: Bearer $RELM_KEY" \
-H "Idempotency-Key: create-ada-2026-07-02" \
-H "Content-Type: application/json" \
-d '{"email":"ada@example.com"}'Pagination
Lists return { object: "list", data, has_more, next_cursor }. Pass the cursor back as ?cursor=… to page forward. Default limit 50, max 200.
Filters
Use ?filter[field][op]=value. Operators: eq, neq, gt, gte, lt, lte, ilike, in. Shorthand ?email=ada@example.com maps to eq.
# Deals worth ≥ $10k in the "won" stage curl "$BASE/v1/deals?filter[stage]=won&filter[value_cents][gte]=1000000" \ -H "Authorization: Bearer $RELM_KEY"
Bulk import
POST /v1/contacts/batch takes up to 500 contacts in one call. Pass dedup_on: ["email"] to upsert on email instead of failing on duplicates.
curl -X POST $BASE/v1/contacts/batch \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{"email":"ada@example.com","first_name":"Ada"},
{"email":"grace@example.com","first_name":"Grace"}
],
"dedup_on": ["email"]
}'Rate limits
Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds). The API version is echoed as Relm-Version.
Quickstart
Create a contact in your language of choice.
curl -X POST $BASE/v1/contacts \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace"
}'Custom fields
Define workspace-scoped custom fields on any core object; values live in metadata and are validated on every write. Types: text | number | date | bool | select.
# Define a required "plan" select field on contacts
curl -X POST $BASE/v1/schema/fields \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{
"object_type": "contact",
"key": "plan",
"label": "Plan",
"type": "select",
"required": true,
"options": { "choices": ["free", "pro", "enterprise"] }
}'
# Now every contact write must include a valid plan
curl -X POST $BASE/v1/contacts \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"ada@example.com","metadata":{"plan":"pro"}}'Manage fields visually on the Schema page.
Associations
First-class polymorphic edges between any two records — contact↔company, contact↔deal, deal↔activity, etc.
curl -X POST $BASE/v1/associations \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_type": "contact", "from_id": "con_...",
"to_type": "deal", "to_id": "deal_...",
"label": "champion"
}'
# List everything linked from a contact
curl "$BASE/v1/associations?from_type=contact&from_id=con_..." \
-H "Authorization: Bearer $RELM_KEY"Webhooks
Subscribe an endpoint and Relm POSTs a signed payload for every matching event. Payloads are signed with HMAC-SHA256 using the endpoint's whsec_… secret; verify the relm-signature header before trusting the body. Failed deliveries retry with exponential backoff up to 8 attempts.
curl -X POST $BASE/v1/webhooks \
-H "Authorization: Bearer $RELM_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/hooks/relm",
"event_types": ["contact.created", "deal.updated"]
}'
# → { "id": "...", "secret": "whsec_…", "url": "...", ... }Signature header format:
relm-signature: t=1751500000,v1=<hex>
# v1 = HMAC_SHA256(secret, `${t}.${rawBody}`)Event types: contact|company|deal|activity.created|updated|deleted. Subscribe to "*" to receive all events.
Endpoints
| POST | /v1/contacts | Create a contact |
| POST | /v1/contacts/batch | Bulk create/upsert (dedup_on: email) |
| GET | /v1/contacts | List contacts |
| GET | /v1/contacts/:id | Retrieve a contact |
| PATCH | /v1/contacts/:id | Update a contact |
| DELETE | /v1/contacts/:id | Delete a contact |
| POST | /v1/companies | Create a company |
| GET | /v1/companies | List companies |
| GET | /v1/companies/:id | Retrieve a company |
| PATCH | /v1/companies/:id | Update a company |
| DELETE | /v1/companies/:id | Delete a company |
| POST | /v1/deals | Create a deal |
| GET | /v1/deals | List deals |
| GET | /v1/deals/:id | Retrieve a deal |
| PATCH | /v1/deals/:id | Update a deal |
| DELETE | /v1/deals/:id | Delete a deal |
| POST | /v1/activities | Create an activity |
| GET | /v1/activities | List activities |
| GET | /v1/activities/:id | Retrieve an activity |
| PATCH | /v1/activities/:id | Update an activity |
| DELETE | /v1/activities/:id | Delete an activity |
| GET | /v1/search?q=... | Search across entities |
| GET | /v1/schema/fields | List custom fields (?object_type=contact) |
| POST | /v1/schema/fields | Define a custom field |
| DELETE | /v1/schema/fields/:id | Remove a custom field |
| GET | /v1/associations | List edges (filter by from_/to_ type+id) |
| POST | /v1/associations | Link two records |
| DELETE | /v1/associations/:id | Unlink |
| GET | /v1/webhooks | List webhook endpoints |
| POST | /v1/webhooks | Subscribe an endpoint (returns signing secret) |
| PATCH | /v1/webhooks/:id | Update an endpoint |
| DELETE | /v1/webhooks/:id | Delete an endpoint |
| POST | /v1/webhooks/:id/replay/:deliveryId | Replay a delivery |
Response shape
Lists return { data, has_more, next_cursor }. Errors return { error: { code, message } } with the appropriate HTTP status.
Objects
id: con_…id: cmp_…id: deal_…id: act_…