Go to Rilla

Custom CRM API

Push appointments and recordings into Rilla from your scheduling system.

Overview

When a person is scheduled for an appointment in your system, send a POST request to the Rilla API. Rilla creates the appointment record and links it for analysis and coaching — either when the user submits a recording through the app, or automatically when you include a recording URL in the request. If a recording and a calendar event were created separately, you can link them after the fact with POST /custom/appointment/match.

Base URL

https://api.apirilla.com

How it works

Admins and Team Managers can create API keys in the Rilla web app. Follow Get an API key for the Settings location and creation steps, or ask your organization’s admin or your Rilla account manager for a key. Store it securely.

POST appointment data (event ID, user email, times, customer info) when events are created or updated in your system.

Include a media_url in the payload to have Rilla automatically ingest a recording for that appointment.

Send a DELETE request with the appointment ID if an event is cancelled so Rilla stays in sync.

Authentication

All requests to the Custom CRM API must include a valid API key in the x-api-key request header.

POST /custom/appointment HTTP/1.1
Host: api.apirilla.com
x-api-key: YOUR_API_KEY
Content-Type: application/json

Request Headers

HeaderTypeRequiredDescription
x-api-keystringYesAPI key given to you by Rilla.
Content-TypestringYesMust be application/json.
AcceptstringNoCustomize response type. Supported: text/plain (default), application/json.

Obtaining an API Key

Admins and Team Managers can create API keys in Settings → Developer. Each key belongs to a single Rilla organization. See Get an API key for a quick overview.

In the Rilla web app, go to Settings → Developer and select the API keys tab.

The API keys table lists your organization's existing keys, their team access, when they were created, and who created them. The secret value is never shown here. Click Create API key.

Choose the team access your integration needs, then click Create API key. Admins can select organization-wide access or specific teams; Team Managers are limited to teams they manage. Rilla generates the key and it is active immediately. See Access scope for the distinction between Data Export scope and CRM authorization.

The key value is displayed exactly once. Click Copy API key and store it somewhere secure, then click I've saved this key. The value can never be viewed again — if it is lost, create a new key and arrange to revoke the old one.

If you have key-deletion access, click the trash icon next to a key and confirm. Otherwise, contact your Rilla account manager to revoke it. Deletion takes effect immediately and cannot be undone: any integration still sending requests with that key loses access.

If you don't have access to Developer settings, ask an admin on your account to create the key for you, or reach out to your Rilla account manager or customer success manager.

Your API key is enforced at the API gateway. If the x-api-key header is missing or not recognized, the request is rejected with 403 Forbidden before it reaches the application. A recognized key that isn't mapped to an active Rilla organization returns 422 Unprocessable Entity.

Keep your key secure

API keys grant access to your organization's data. Do not expose them in client-side code, public repositories, or logs. Contact support@rilla.com immediately if a key is ever compromised.

POST /custom/appointment

Creates a new appointment or updates an existing one. Rilla uses event_id to determine whether to insert or update.

Request Headers

HeaderTypeRequiredDescription
x-api-keystringYesYour Rilla API key.
Content-TypestringYesMust be application/json.
AcceptstringNotext/plain (default) or application/json.

Request Body

FieldTypeRequiredDescription
event_idstringYesID for the event.
user_emailstringYesEmail of the rep who has the appointment. Used to match to the correct Rilla user.
attendee_emailsstring[]NoAdditional attendee emails. Send the full list on every update. See Multiple Attendees.
start_timestringYesStart of the event (UTC): "yyyy-mm-dd hh:mm:ss".
end_timestringYesEnd of the event (UTC): "yyyy-mm-dd hh:mm:ss".
titlestringYesTitle of the event. Recommend including the customer name.
customerobjectYesCustomer/contact info. See Customer Object.
resultstringNoOutcome of the appointment (e.g. "won", "lost").
typestringNoThe type of appointment.
pricefloatNoThe price quoted in the appointment.
media_urlstringNoURL of a hosted audio file. If provided, Rilla streams and processes the recording automatically. Cannot be combined with a non-empty attendee_emails list.
recording_typestringNoRequired when media_url is provided. One of: CALL_AUDIO, APPOINTMENT_AUDIO, CALL_AUDIO_WITH_LOCATION. See Recording Types.
locationstringNoRequired when recording_type is CALL_AUDIO_WITH_LOCATION. Determines Voice-ID assignment group.
lead_sourcestringNoSource of the lead (e.g. "Salesforce").
custom_fieldsobjectNoAny additional fields to store in Rilla for this appointment.

start_time and end_time must be in UTC. If not sent in UTC, appointments will not match and users will receive notifications at the wrong time.

How times are displayed

Rilla stores start_time and end_time as UTC, exactly as you send them, and displays them in local time in the app and the Custom API Debugger. A time sent as midnight UTC appears as the previous evening in US time zones. For example, 2024-06-22 00:00:00 UTC shows as Jun 21, 8:00 PM Eastern. The stored value is unchanged; only the display is converted. To make an appointment read as a specific local time, send the UTC equivalent of that local time.

Example — Create

{
  "event_id": "appt-1234",
  "user_email": "rep@yourcompany.com",
  "start_time": "2024-06-15 14:00:00",
  "end_time": "2024-06-15 15:00:00",
  "title": "Home Estimate - Jane Smith",
  "customer": {
    "id": "cust-567",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "555-867-5309"
  },
  "result": "won",
  "price": 4500.00,
  "lead_source": "Facebook"
}

Updating an Appointment

To update an event, use the same POST endpoint. Only event_id and user_email are required. Send the full attendee_emails list on every update to keep additional attendees: omitting it or sending [] replaces the attendees with just user_email. Other omitted fields remain unchanged.

{
  "event_id": "appt-1234",
  "user_email": "rep@yourcompany.com",
  "result": "lost"
}

Outcomes are created automatically

result is free-form. Rilla matches the value to your organization's existing outcomes (case-insensitively) and creates a new outcome if it doesn't match any. There's no fixed list to pick from, so send a consistent set of values. A typo like "Pendng Sale" creates a new, separate outcome.

Multiple Attendees

When the appointment owner and the person conducting it may differ, keep the owner in user_email and include other reps in attendee_emails. Each resolved person becomes an attendee, making the appointment eligible for their recording matching and appointment selection. Send the appointment before the rep records; adding attendees does not rematch existing recordings.

{
  "event_id": "appt-1234",
  "user_email": "owner@yourcompany.com",
  "attendee_emails": ["consultant@yourcompany.com", "host@yourcompany.com"],
  "start_time": "2026-09-20 14:00:00",
  "end_time": "2026-09-20 15:00:00",
  "title": "Home Estimate - Jane Smith",
  "customer": {"id": "cust-567", "name": "Jane Smith"}
}
  • Addresses are trimmed and matched case-insensitively within your API key's organization, using primary email, alternate email, then an unambiguous first.last email prefix. Duplicate addresses or aliases for the same person produce one attendee.
  • With a non-empty attendee_emails list, removed and hidden users are skipped. Unresolved addresses, including the owner, are skipped with a warning in Rilla's logs. At least one person must resolve; otherwise the request returns 422 without changing the appointment. Omitting the field or sending [] retains the legacy single-rep lookup behavior.
  • user_email remains required and must be a non-empty string. attendee_emails must be an array of non-empty strings; null, a single string, or invalid entries return 400.
  • Every update replaces the complete attendee list. Re-send everyone who should remain, even on an outcome-only or time-only update. Send [] to return to the owner alone.
  • A non-empty attendee_emails list cannot be combined with media_url (400); multi-attendee appointments are for reps recording through the app. Location-based call imports continue to use their system-managed UNASSIGNED account.

For example, this outcome-only update preserves the owner and both additional attendees from the request above:

{
  "event_id": "appt-1234",
  "user_email": "owner@yourcompany.com",
  "attendee_emails": ["consultant@yourcompany.com", "host@yourcompany.com"],
  "result": "won"
}

The API returns plain-text errors for these attendee-specific failures:

StatusResponse bodyWhat to change
400user_email must be a non-empty stringSupply a non-empty owner email.
400attendee_emails must be an array of non-empty stringsUse an array; remove blank or non-string entries.
400attendee_emails cannot be combined with media_urlDo not combine hosted audio with additional attendees.
422User email is not in RillaNone of the supplied addresses resolved. Check the owner and additional attendees in your API key's organization; the appointment has not been changed.

Responses

StatusDescription
200 OKThe appointment was created or updated successfully.
400 Bad RequestMalformed request or missing required fields.
403 Forbiddenx-api-key is missing or not recognized (enforced at the API gateway).
422 Unprocessable EntityValid JSON but cannot be processed (e.g. no supplied attendee resolves in your organization).
500 Internal Server ErrorUnexpected server error.

DELETE /custom/appointment/{id}

Deletes an appointment from Rilla. No request body is needed. Use this when an appointment is cancelled in your system to keep data in sync.

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe event_id used when the appointment was originally created.

Request Headers

HeaderTypeRequiredDescription
x-api-keystringYesYour Rilla API key.

Example Request

DELETE /custom/appointment/appt-1234 HTTP/1.1
Host: api.apirilla.com
x-api-key: YOUR_API_KEY

Responses

StatusDescription
200 OKThe appointment was deleted successfully.
403 Forbiddenx-api-key is missing or not recognized.
404 Not FoundNo appointment with that ID exists for your organization.
409 ConflictThe appointment has a recording linked to it and cannot be deleted.
500 Internal Server ErrorUnexpected server error.

POST /custom/appointment/match

Links a calendar event Rilla already has on file for your organization to an existing Rilla conversation. Use this when a recording and a calendar event were created separately — for example, the rep recorded through the app before the appointment was pushed — and you want to associate them after the fact.

Both records must already exist. This endpoint creates and updates nothing else: it only sets the matched conversation's recording appointment_id to the calendar event. To create or update the appointment itself, use POST /custom/appointment.

Matching is idempotent — re-matching a conversation overwrites any previous link, so it's safe to retry. All lookups run before anything is written, so a request that fails validation changes nothing.

Request Headers

HeaderTypeRequiredDescription
x-api-keystringYesYour Rilla API key.
Content-TypestringYesMust be application/json.
AcceptstringNotext/plain (default) or application/json.

Request Body

FieldTypeRequiredDescription
event_idstringYesThe external ID of a calendar event Rilla already has on file for your organization — typically one you sent via POST /custom/appointment.
conversation_idstring (UUID)YesID of an existing Rilla conversation to link — for example, a conversation_id received from a webhook or the Data Export API.

Example Request

POST /custom/appointment/match HTTP/1.1
Host: api.apirilla.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
  "event_id": "appt-1234",
  "conversation_id": "f1a3e9d8-2b46-4c1a-9c7e-7e4a18d2c93f"
}

Responses

On success the response body is the plain string Successfully matched conversation to event.

StatusBodyWhen
200 OKSuccessfully matched conversation to eventThe conversation's recording was linked to the event.
400 Bad RequestInvalid JSON dataThe request body is not valid JSON.
400 Bad RequestMissing some required parameters in the bodyevent_id or conversation_id is missing.
404 Not FoundEvent not foundNo calendar event with that event_id exists for your organization.
404 Not FoundConversation not foundNo conversation with that conversation_id exists in your organization.
403 ForbiddenForbiddenx-api-key is missing or not recognized (enforced at the API gateway).
422 Unprocessable EntityConversation has no recording to linkThe conversation exists but has no recording attached.
422 Unprocessable EntityConversation's recording could not be found to linkThe conversation's recording could not be found while linking.
500 Internal Server Error—Unexpected server error.

Cross-org lookups return 404

A conversation_id that belongs to a different organization returns the same Conversation not found response as one that doesn't exist. Rilla never reveals whether a conversation exists outside your organization.

Data Models

Appointment Object

FieldTypeDescription
event_idstringID for the event.
user_emailstringEmail of the rep who has the appointment.
attendee_emailsstring[]Optional additional attendee emails; re-send the complete list on every update.
start_timestringStart of the event (UTC): "yyyy-mm-dd hh:mm:ss".
end_timestringEnd of the event (UTC): "yyyy-mm-dd hh:mm:ss".
titlestringTitle of the event.
customerobjectCustomer/contact info.
customer.idstringID for the customer.
customer.namestringFull name of the customer.
customer.emailstringEmail of the customer.
customer.phonestringPhone number of the customer.
customer.addressstringAddress of the customer.
resultstringOutcome of the appointment (e.g. "won", "lost").
typestringThe type of appointment.
pricefloatPrice quoted in the appointment.
media_urlstringURL of a hosted audio file; incompatible with a non-empty attendee_emails list.
recording_typestringOne of: CALL_AUDIO, APPOINTMENT_AUDIO, CALL_AUDIO_WITH_LOCATION.
locationstringVoice-ID assignment group (used with CALL_AUDIO_WITH_LOCATION).
lead_sourcestringSource of the lead.
custom_fieldsobjectAdditional custom fields.

Customer Object

FieldTypeRequiredDescription
idstringYesID for the customer.
namestringYesFull name of the customer.
emailstringNoEmail of the customer.
phonestringNoPhone number of the customer.
addressstringNoAddress of the customer.

Recording Types

TypeDescription
CALL_AUDIOThe recording is a dialer call.
APPOINTMENT_AUDIOThe recording is an in-person appointment.
CALL_AUDIO_WITH_LOCATIONDialer call with Voice-ID assignment via location.

Code Examples

Create an Appointment

import requests

url = "https://api.apirilla.com/custom/appointment"
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
payload = {
    "event_id": "appt-1234",
    "user_email": "rep@yourcompany.com",
    "start_time": "2024-06-15 14:00:00",
    "end_time": "2024-06-15 15:00:00",
    "title": "Home Estimate - Jane Smith",
    "customer": {
        "id": "cust-567",
        "name": "Jane Smith",
        "email": "jane@example.com",
        "phone": "555-867-5309"
    },
    "result": "won",
    "price": 4500.00,
    "lead_source": "Facebook"
}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code, response.text)
const response = await fetch('https://api.apirilla.com/custom/appointment', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    event_id: 'appt-1234',
    user_email: 'rep@yourcompany.com',
    start_time: '2024-06-15 14:00:00',
    end_time: '2024-06-15 15:00:00',
    title: 'Home Estimate - Jane Smith',
    customer: { id: 'cust-567', name: 'Jane Smith' },
    result: 'won',
    price: 4500.00,
  }),
})
curl -X POST https://api.apirilla.com/custom/appointment \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "appt-1234",
    "user_email": "rep@yourcompany.com",
    "start_time": "2024-06-15 14:00:00",
    "end_time": "2024-06-15 15:00:00",
    "title": "Home Estimate - Jane Smith",
    "customer": {"id": "cust-567", "name": "Jane Smith"},
    "result": "won"
  }'

Update an Appointment

POST to the same endpoint with event_id, user_email, and the fields you want to change. For multi-attendee appointments, also re-send the full attendee_emails list on every update. Other omitted fields remain unchanged.

import requests

url = "https://api.apirilla.com/custom/appointment"
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
payload = {
    "event_id": "appt-1234",
    "user_email": "rep@yourcompany.com",
    "result": "lost"
}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code, response.text)
const response = await fetch('https://api.apirilla.com/custom/appointment', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    event_id: 'appt-1234',
    user_email: 'rep@yourcompany.com',
    result: 'lost',
  }),
})
curl -X POST https://api.apirilla.com/custom/appointment \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event_id": "appt-1234", "user_email": "rep@yourcompany.com", "result": "lost"}'

Delete an Appointment

import requests

event_id = "appt-1234"
url = f"https://api.apirilla.com/custom/appointment/{event_id}"
headers = {"x-api-key": "YOUR_API_KEY"}
response = requests.delete(url, headers=headers)
print(response.status_code, response.text)
const eventId = 'appt-1234'
const response = await fetch(
  `https://api.apirilla.com/custom/appointment/${eventId}`,
  {
    method: 'DELETE',
    headers: { 'x-api-key': 'YOUR_API_KEY' },
  }
)
curl -X DELETE https://api.apirilla.com/custom/appointment/appt-1234 \
  -H "x-api-key: YOUR_API_KEY"

Match a Conversation to an Event

Link an existing conversation to a calendar event Rilla already has for your organization. Both must already exist.

import requests

url = "https://api.apirilla.com/custom/appointment/match"
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}
payload = {
    "event_id": "appt-1234",
    "conversation_id": "f1a3e9d8-2b46-4c1a-9c7e-7e4a18d2c93f"
}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code, response.text)
const response = await fetch('https://api.apirilla.com/custom/appointment/match', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    event_id: 'appt-1234',
    conversation_id: 'f1a3e9d8-2b46-4c1a-9c7e-7e4a18d2c93f',
  }),
})
curl -X POST https://api.apirilla.com/custom/appointment/match \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event_id": "appt-1234", "conversation_id": "f1a3e9d8-2b46-4c1a-9c7e-7e4a18d2c93f"}'

Custom API Debugger

Once your integration is sending events, you can verify them inside Rilla's web app. The Custom API Debugger lists every event we received against your API key and shows the full payload exactly as Rilla stored it. Use it to confirm field values, troubleshoot mappings, and check timestamps without needing access to logs.

Paste the same x-api-key you use to call the API, then click Continue. The key is only used in your browser session to fetch the events for your organization. It is not stored.

The event table shows Start time, Rep, and Title. Open an event's details to see the remaining fields.

Use the search box to filter by rep name, client name, or appointment title. The Refresh button re-fetches the latest events.

Click a row to open its details modal. It shows the IDs, appointment fields, rep, client, recording information, any custom_fields you sent, and the exact timestamps Rilla recorded — useful for confirming that what you sent matches what we stored. Closing the details leaves Developer settings open.

If an event isn't here

If you sent a request and don't see the event in the debugger, the API likely rejected it. Check the response status from your POST. A 4xx response means the event never reached storage. See Error Codes for what each status means.

Error Codes

The API returns standard HTTP status codes. On error, the response body is a plain string describing the issue.

CodeStatusDescription
200OKThe request was processed successfully.
400Bad RequestMalformed request or missing required fields. Common causes: event_id, user_email, or title missing; customer object missing or incomplete; timestamp format invalid; media_url provided with CALL_AUDIO_WITH_LOCATION but location missing; body is not valid JSON; user_email is not a non-empty string; attendee_emails is not an array of non-empty strings or a non-empty list is combined with media_url. On /custom/appointment/match: event_id or conversation_id missing.
403Forbiddenx-api-key is missing or not recognized. Enforced at the API gateway, before the request reaches the application.
404Not FoundEither the URL path or HTTP method doesn't match a route (body is {"message": "No matching route..."}, see the callout below), or a targeted record was not found in your organization. The latter is returned by DELETE /custom/appointment/{id} (appointment ID not found) and POST /custom/appointment/match (event_id or conversation_id not found).
409ConflictReturned by DELETE /custom/appointment/{id} when the appointment has a recording linked to it. Rilla keeps the appointment rather than deleting it.
422Unprocessable EntityValid JSON but cannot be processed. Common causes: none of the supplied attendee emails resolve in your organization; a recognized key not mapped to an organization. With attendee_emails omitted or [], user_email must resolve. On /custom/appointment/match: the conversation has no recording to link.
500Internal Server ErrorUnexpected server error. The event is forwarded to a dead-letter queue for investigation.

404 with 'No matching route'

If the response body is {"message": "No matching route..."}, the URL path or HTTP method didn't match an endpoint. This is not an authentication problem, even if your x-api-key is set. Check the exact path (/custom/appointment, /custom/appointment/{id}, or /custom/appointment/match), the HTTP method, and watch for trailing slashes or spaces.

400, 403, 404, 409, and 422 errors are permanent — retrying the same payload will not help. Fix the data first. 500 errors may be transient — retry with exponential backoff.

Rate Limits

Effective Custom CRM limits depend on your API gateway usage-plan configuration. Confirm the applicable per-key quota and burst limit with Rilla before high-volume use. Requests exceeding an applicable limit can receive 429 Too Many Requests.

High-volume Usage

If your integration requires high-throughput or bulk operations, reach out to support@rilla.com before going live.

Best Practices

  • Retry with exponential backoff on 429 or 5xx responses.
  • Avoid polling at high frequency.
  • Batch appointment creation on your side before calling the API.
On this page