This document defines the REST interface a CRM Integration Service must expose. The service is a proxy: it sits between the desktop client and your CRM, and it is written and hosted by you. We do not see inside it. The platform only needs the endpoints below to answer in the shapes described, and everything behind them — how you authenticate to your own CRM, how you cache, how you queue — is yours to decide.
A word on the data. Your service will handle call recordings and customer records. That data is your responsibility once it reaches you, and the design of this interface deliberately does not constrain how you store or protect it.
Conventions
| Marker | | | Meaning |
|---|
| OPTIONAL | | | The endpoint may be left unimplemented. The client copes with its absence. |
| ASYNC | | | Processing is expected to run asynchronously. Return a status object immediately and let the caller poll it, rather than holding the connection open. |
| Authorization | | | Every endpoint except GET /token and GET /crm expects the token in an X-CrmIService-Token request header. How you issue and check that token is your decision; the platform only carries it. |
| Types | | | All request and response bodies are JSON. A field marked can be empty may be returned as null or as an empty string. |
Authentication
Issue a token, then expect it back on every later request in the X-CrmIService-Token header. Whether that token is a session key, a JWT or a random string is up to you.
Token object
| Field | Type | Can be empty | Description |
|---|
| id | string | no | The token supplied on subsequent requests. |
Example
{
"id": "1f3870be274f6c49b3e31a0c6728957f"
}
GET /token
Authenticate the user against the CRM and return a token for subsequent requests.
Request
$ curl -X GET https://{crmiservice-host}/token \
-u john.goodley@woodwork.com:password8RKOtgTg3biYi5kag5UHx3Mrv
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns a Token object. |
| 401 Unauthorized | CRM authentication failed. |
{
"id": "1480fd6bf83a1e65c470b8fd37ee43b5"
}
Reference implementation
handler [ GET /token ]
if CRM authentication fails
return 401
return <JSON<Token>> generated token, 200
Search
These endpoints answer the question the desktop client asks on every inbound call: who is this. Fields map onto whatever your CRM calls its records — Lead, Contact, Account.
Customer object
| Field | Type | Can be empty | Description |
|---|
| id | string | no | Record ID in the CRM. |
| type | "Contact" | "Lead" | "Account" | "UNKNOWN" | no | Which kind of CRM record this is. |
| webpage | string | no | URL that opens this record in the CRM. |
| name | string | yes | First and last name. |
| email | string | yes | Email address. |
| company | string | yes | Company name. |
| mobilephone | string | yes | Mobile number. |
| workphone | string | yes | Work number. Note: the upstream specification’s attribute table misspells this wokrphone, while every worked example in the same document uses workphone. Implement workphone. |
| homephone | string | yes | Home number. |
| fax | string | yes | Fax number. |
Example
{
"id": "0032000001DrFDWAA3",
"type": "Contact",
"webpage": "https://emea.salesforce.com/0032000001DrFDWAA3",
"name": "Andy Young",
"email": "andy.young@mail.com",
"company": "Salesforce Inc",
"mobilephone": "(785) 265-5350",
"workphone": "(785) 241-6200",
"homephone": null,
"fax": null
}
GET /customers
Return every callable record in the CRM — accounts, contacts and leads.
Request
$ curl -X GET https://{crmiservice-host}/customers \
-H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns an array of Customer objects. |
| 401 Unauthorized | Invalid or missing token. |
Reference implementation
handler [ GET /customers ]
if request is unauthorized
return 401
if CRM lookup errors
return the CRM status code
return <JSON<List<Customer>>> customers, 200
GET /customers/search
Return every record whose number matches. Partial matches are expected — the client passes the digits it has.
Request
$ curl -X GET "https://{crmiservice-host}/customers/search?phonenumber=241-6200" \
-H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns an array of Customer objects. |
| 400 Bad Request | Missing or invalid query parameter. |
| 401 Unauthorized | Invalid or missing token. |
Reference implementation
handler [ GET /customers/search ]
if request is unauthorized
return 401
if 'phonenumber' is not set
return 400
if 'phonenumber' is empty
fall through to handler [ GET /customers ]
if CRM lookup errors
return the CRM status code
return <JSON<List<Customer>>> customers, 200
Users
A user is the agent on your side of the call — the person using the desktop client, matched to their identity in the CRM.
User object
| Field | Type | Can be empty | Description |
|---|
| id | string | no | User ID in the CRM. |
| username | string | no | Username on the CRM platform. |
| agentnumber | string | yes | Agent number associated with the user. |
| name | string | yes | First and last name. |
| email | string | yes | Email address. |
| mobilephone | string | yes | Mobile number. |
| workphone | string | yes | Work number. |
| fax | string | yes | Fax number. |
Example
{
"id": "00520000003nZ4QAAU",
"username": "maxijazz@faithless.com",
"agentnumber": null,
"name": "Maxwell Fraser",
"email": "maxijazz@faithless.com",
"mobilephone": null,
"workphone": null,
"fax": null
}
GET /users/searchOPTIONAL
Return the single CRM user matching the username exactly. No pattern matching, and never more than one result.
Request
$ curl -X GET "https://{crmiservice-host}/users/search?username=agent007@secretagency.com" \
-H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns a User object. |
| 400 Bad Request | Missing or invalid query parameter. |
| 401 Unauthorized | Invalid or missing token. |
Reference implementation
handler [ GET /users/search ]
if request is unauthorized
return 401
if 'username' is not set or empty
return 400
if CRM lookup errors
return the CRM status code
return <JSON<User>> user, 200
Correction to the upstream specification. The source document shows this endpoint returning a list of Customer objects, and labels its reference implementation GET /customers/search. Both are copy-and-paste errors in that document. This endpoint returns a single User.
CRM information
Identifies which CRM is behind the integration. This endpoint is not authorized — it carries nothing sensitive.
Crm object
| Field | Type | Can be empty | Description |
|---|
| name | string | no | Name of the CRM platform — Salesforce, ZOHO, HubSpot and so on. |
| url | string | no | URL of the CRM home page. |
| version | string | no | Version of the CRM platform or of its API. |
Example
{
"name": "Salesforce",
"url": "https://login.salesforce.com",
"version": "14.00"
}
GET /crm
Return basic information about the integrated CRM.
Request
$ curl -X GET https://{crmiservice-host}/crm
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns a Crm object. |
| 404 Not Found | No CRM information available. |
Reference implementation
handler [ GET /crm ]
if CRM lookup errors
return the CRM status code
return <JSON<Crm>> crm, 200
Call logs and recordings
If your CRM has no concept of call logging, still expose these endpoints. Answer with success and a status of READY; the status object itself may be empty. Silence here stalls the client.
CallLogRequest object
| Field | Type | Can be empty | Description |
|---|
| customerid | string | yes | Caller’s record ID in the CRM. |
| customertype | "Contact" | "Lead" | "Account" | "UNKNOWN" | yes | Which kind of CRM record the caller is. |
| subject | string | no | Call subject. |
| phonenumber | string | yes | The other party’s number, caller or callee depending on direction. |
| direction | "OUTBOUND" | "INBOUND" | no | Direction of the call. |
| duration | int | no | Call duration in seconds. |
| starttime | int | no | UNIX timestamp of the call start. |
| status | "ANSWERED" | "UNANSWERED" | "BUSY" | "UNAVAILABLE" | "INPROGRESS" | no | State of the call. |
| description | string | no | Call description. |
| asteriskcallid1 | string | no | Caller’s channel ID. |
| asteriskcallid2 | string | no | Callee’s channel ID. |
| recordupload | 1 | 0 | no | Whether the recording should be uploaded to the CRM. |
| recordname | string | yes | Recording file name. |
| recorddesc | string | yes | Recording description. |
| recording_url | string | yes | Direct URL to the self-care portal page for this call. After signing in, the user reaches the CDR entry for the call and all its legs, and can play or download the recording. Supported by desktop client 7.5 and above. |
Example
{
"customerid": "0032000001DrFDSAA3",
"customertype": "Contact",
"subject": "API call",
"phonenumber": "202 3893-293",
"direction": "OUTBOUND",
"duration": 0,
"starttime": 1684931119,
"status": "UNANSWERED",
"description": "Unanswered call by customer",
"asteriskcallid1": "1407350434.61",
"asteriskcallid2": "1407350434.62",
"recordupload": 0,
"recordname": "",
"recorddesc": "",
"recording_url": ""
}
CallLogResponse object
| Field | Type | Can be empty | Description |
|---|
| id | string | no | Call log ID in the CRM. |
| userid | string | yes | User ID in the CRM. |
| customerid | string | no | Caller’s record ID in the CRM. |
| customertype | "Contact" | "Lead" | "Account" | "UNKNOWN" | no | Which kind of CRM record the caller is. |
| subject | string | no | Call subject. |
| phonenumber | string | no | The other party’s number. |
| direction | "OUTBOUND" | "INBOUND" | no | Direction of the call. |
| duration | int | no | Call duration in seconds. |
| starttime | int | no | UNIX timestamp of the call start. |
| status | "ANSWERED" | "UNANSWERED" | "BUSY" | "UNAVAILABLE" | "INPROGRESS" | no | State of the call. |
| description | string | yes | Call description. |
| asteriskcallid1 | string | yes | Caller’s channel ID. |
| asteriskcallid2 | string | yes | Callee’s channel ID. |
| recordname | string | yes | Recording file name. |
| recorddesc | string | yes | Recording description. |
| recording_url | string | yes | Direct URL to the recording in the self-care portal. |
Example
{
"id": "23487cdc093e810a01ff0",
"userid": "",
"customerid": "0032000001DrFDSAA3",
"customertype": "Contact",
"subject": "API call",
"phonenumber": "202 3893-293",
"direction": "OUTBOUND",
"duration": 0,
"starttime": 1684931119,
"status": "UNANSWERED",
"description": "Unanswered call by customer",
"asteriskcallid1": "1407350434.61",
"asteriskcallid2": "1407350434.62",
"recordname": "",
"recorddesc": "",
"recording_url": ""
}
Status object
| Field | Type | Can be empty | Description |
|---|
| id | string | no | Status ID for the resource. |
| status | "PENDING" | "FAILED" | "CANCELED" | "READY" | no | Current state of processing. |
| timestamp | int | no | UNIX timestamp of the move into READY. |
| timetolive | int | no | Seconds the resource stays available after reaching READY. |
| resourcetype | "CallLog" | "CallRecord" | no | Type of resource being processed. |
| resourceid | string | yes | Resource ID. Must not be empty once status is READY. |
| message | string | yes | Additional status information. |
Example
{
"id": "c285ac7b-8784-4894-923f-4769ae2e1261",
"status": "READY",
"timestamp": 1684499360,
"timetolive": 86400,
"resourcetype": "CallLog",
"resourceid": "00T2000001uiCveEAE",
"message": ""
}
Query parameters on POST /calllog
| Parameter | Default | Meaning |
|---|
| async | "yes" | Whether to process asynchronously. "yes" or "no". |
| statusid | "" | A status ID the client received from an earlier request. |
Supporting both is recommended. If you cannot, behave as though the defaults were passed.
POST /calllogASYNC
Create a call log in the CRM from the posted CallLogRequest. When processing asynchronously, start the work, generate a status ID and return immediately.
Request
$ curl -X POST https://{crmiservice-host}/calllog \
-H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5" \
-d '{
"customerid": "",
"customertype": "",
"subject": "API call",
"phonenumber": "232 8932-225",
"direction": "INBOUND",
"duration": 93,
"starttime": 1684497234,
"status": "ANSWERED",
"description": "Answered call by agent at support call center",
"asteriskcallid1": "1407351358.61",
"asteriskcallid2": "1407351358.62",
"recordupload": 0,
"recordname": "",
"recorddesc": ""
}'
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns a Status object. |
| 400 Bad Request | Invalid JSON body or query parameters. |
| 401 Unauthorized | Invalid or missing token. |
| 409 Conflict | Failed to create the call log. Synchronous requests only. |
{
"id": "c285ac7b-8784-4894-923f-4769ae2e1261",
"status": "PENDING",
"timestamp": 1684499240,
"timetolive": 86400,
"resourcetype": null,
"resourceid": null
}
Reference implementation
handler [ POST /calllog ]
if request is unauthorized
return 401
if posted body is invalid
return 400
if 'statusid' is not set
'statusid' = ""
if 'async' is not set
'async' = true
if no stored record for 'statusid'
store a new record keyed by a generated status ID
if 'async'
run the CRM write in the background against 'statusid'
mark 'statusid' PENDING
return <JSON<Status>> stored record, 200
if the CRM write errors
mark 'statusid' FAILED
return 409
mark 'statusid' READY
return <JSON<Status>> stored record, 200
GET /status/{id}
Return the current processing state of a resource.
Request
$ curl -X GET https://{crmiservice-host}/status/c285ac7b-8784-4894-923f-4769ae2e1261 \
-H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"
Response
| Status | Meaning |
|---|
| 200 OK | Success. Returns a Status object. |
| 401 Unauthorized | Invalid or missing token. |
| 404 Not Found | No such status or resource. |
{
"id": "c285ac7b-8784-4894-923f-4769ae2e1261",
"status": "READY",
"timestamp": 1684499266,
"timetolive": 86400,
"resourcetype": "CallLog",
"resourceid": "23487cdc093e810a01ff0"
}
Reference implementation
handler [ GET /status/{id} ]
if request is unauthorized
return 401
if no stored record for the ID
return 404
return <JSON<Status>> stored record, 200
Notes
- Every name, number and record ID in this document is invented for illustration. None refers to a real person.
- Your platform is most likely carrying live calls while you build against this interface. Load generated by testing lands on the same system your users are on.
- Authentication between your integration service and your CRM is entirely yours to design. The platform knows only a username, a password and the token you issue; it relies on you to make the exchange secure.
Questions on this document
Send them to support@ahoytel.com and they reach the engineers who run the platform, not a queue. If something here does not match what your service actually receives, tell us — that is a defect in this document and we would rather fix it than have you work around it.