Calls
SMS reasons
The rules that decide when your assistant texts a caller mid-call and exactly what it sends: create, list, update, and delete them.
An SMS reason is a rule with three working parts: the situation that should trigger a text, the line the assistant speaks before sending it, and the message body that actually goes out. Stored, it also carries a label and the assistant it belongs to (five fields in all). These rules apply during a voice call. Inbound texting on your own number is a different feature, the SMS channel. This page is the resource itself: the five endpoints that create and maintain those rules. The generated field-by-field reference lives at SMS Reasons API.
| Method | Path | Purpose |
|---|---|---|
| GET | /sms-reasons | List SMS reasons, filterable by assistant |
| POST | /sms-reasons | Create an SMS reason |
| GET | /sms-reasons/{id} | Get one SMS reason |
| PUT | /sms-reasons/{id} | Update an SMS reason |
| DELETE | /sms-reasons/{id} | Delete an SMS reason |
All five take an API key in the Authorization header (see Authentication) and all five return the standard response envelope: success, then data, then metadata carrying timestamp, version, and path.
These rules govern texts sent during a voice call. They are separate from the SMS channel, which answers inbound texts on your number and is turned on with PATCH /assistants/{id}/sms-enabled. Enabling that channel does not create SMS reasons, and creating a reason does not turn the channel on.
SMS reason fields#
Every SMS reason (whether returned from a list, a fetch, a create, an update, or a delete) carries the same fields.
| Field | Description |
|---|---|
id | The SMS reason's identifier, assigned by OnCore. |
name | A friendly label for your own use, e.g. "Car Rental Price List". |
reason | The trigger that causes the assistant to send the text, e.g. "When the caller asks for available cars and wants to see pricing." |
message | What the assistant says out loud before sending, e.g. "Sure, I'll send you a price list via SMS right now." |
content | The SMS body the caller receives, e.g. "Here's the price list for tomorrow: https://example.com/prices" |
assistantId | The assistant this reason is attached to. |
properties | Used internally by OnCore. You do not need to set it. |
createdAt, updatedAt | Record timestamps. |
The pair worth getting right is message and content. message is spoken: it is what the caller hears on the phone. content is written: it is the body of the SMS itself. Both are stored text rather than something composed per call, so every send of a given reason delivers the same body until you change it with PUT. The link, price list, or address belongs in content; message only announces that it is coming.
properties is missing from the JSON examples on purpose
The spec defines no shape for properties, and this page will not print an invented value. Every response on this page really does include it, so if you are generating a type from the examples below, add properties yourself.
properties is set by the platform, not by you
properties is an object with arbitrary keys, and it is required on every write: send
{} if you have nothing to put in it. The create endpoint's own description says the
field is used internally and does not need to be filled in manually, so treat it as
platform-owned: echo back what you read rather than composing one.
List SMS reasons#
GET /sms-reasons
| Query param | Type | Required | Description |
|---|---|---|---|
limit | number | No | Page size. Default 50, maximum 500. |
offset | number | No | Items to skip. Default 0. |
assistantId | UUID | No | Only SMS reasons attached to this assistant. |
Most of the time you want the reasons for one assistant, so assistantId is the filter you will reach for.
curl "https://core-api.heysadie.ai/sms-reasons?assistantId=0195f1e2-0000-0000-0000-000000000000&limit=100" \
-H "Authorization: ApiKey YOUR_API_KEY"The page arrives in data.smsReasons, and data.total is the number of reasons matching your filter. Advance offset until you have seen that many.
{
"success": true,
"data": {
"smsReasons": [
{
"id": "0195f1e2-1111-0000-0000-000000000000",
"name": "Car Rental Price List",
"reason": "When the caller asks for available cars and wants to see pricing.",
"message": "Sure, I will send you a price list via SMS right now.",
"content": "Price list for tomorrow: https://example.com/prices",
"assistantId": "0195f1e2-0000-0000-0000-000000000000",
"createdAt": "2026-07-30T12:00:00.000Z",
"updatedAt": "2026-07-30T12:00:00.000Z"
}
],
"total": 1
},
"metadata": {
"timestamp": "2026-07-30T12:00:00.000Z",
"version": "1.0",
"path": "/sms-reasons"
}
}Create an SMS reason#
POST /sms-reasons
| Field | Type | Description |
|---|---|---|
name | string | A friendly label for this SMS reason. |
reason | string | The event or trigger that causes this SMS to be sent. |
message | string | What the assistant says before sending. |
content | string | The actual SMS message sent to the caller. |
assistantId | UUID | The assistant this reason is associated with. |
The body also accepts properties, which OnCore uses internally; you do not need to send it.
Neither request-body table on this page carries a Required column, because the spec does not supply one:
Every field is required
All six fields (assistantId, name, reason, message, content and properties)
must be present on create. Omitting any one is rejected with 400, and unknown fields are
rejected rather than ignored, so a misspelled key fails the whole request.
The API declares no length limit on content. A body long enough to be delivered as more
than one segment is not rejected, so keep it short enough to arrive as a single message
unless you have confirmed the carrier behaviour you want.
curl -X POST https://core-api.heysadie.ai/sms-reasons \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Car Rental Price List",
"reason": "When the caller asks for available cars and wants to see pricing.",
"message": "Sure, I will send you a price list via SMS right now.",
"content": "Price list for tomorrow: https://example.com/prices",
"assistantId": "0195f1e2-0000-0000-0000-000000000000"
}'A successful create returns 200, not 201, with the stored reason (including its new id) in data.
{
"success": true,
"data": {
"id": "0195f1e2-1111-0000-0000-000000000000",
"name": "Car Rental Price List",
"reason": "When the caller asks for available cars and wants to see pricing.",
"message": "Sure, I will send you a price list via SMS right now.",
"content": "Price list for tomorrow: https://example.com/prices",
"assistantId": "0195f1e2-0000-0000-0000-000000000000",
"createdAt": "2026-07-30T12:00:00.000Z",
"updatedAt": "2026-07-30T12:00:00.000Z"
},
"metadata": {
"timestamp": "2026-07-30T12:00:00.000Z",
"version": "1.0",
"path": "/sms-reasons"
}
}Add one reason per scenario rather than stretching a single reason to cover several. reason is the trigger text and content is the body that goes with it, so a pricing enquiry and a directions request want separate rules.
Get one SMS reason#
GET /sms-reasons/{id}
| Path param | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The SMS reason to fetch. |
curl https://core-api.heysadie.ai/sms-reasons/0195f1e2-1111-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY"data is a single SMS reason with the same fields as the create response: id, name, reason, message, content, assistantId, properties, createdAt, and updatedAt. An id that does not exist returns 404 with RESOURCE_NOT_FOUND.
Update an SMS reason#
PUT /sms-reasons/{id}
| Path param | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The SMS reason to update. |
The request body takes the same fields as create:
| Field | Type | Description |
|---|---|---|
name | string | A friendly label for this SMS reason. |
reason | string | The event or trigger that causes this SMS to be sent. |
message | string | What the assistant says before sending. |
content | string | The actual SMS message sent to the caller. |
assistantId | UUID | The assistant this reason is associated with. |
properties is accepted here too, and is still the internal field you do not set.
PUT is the only update verb on this resource; the API exposes no PATCH /sms-reasons/{id}.
curl -X PUT https://core-api.heysadie.ai/sms-reasons/0195f1e2-1111-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Car Rental Price List",
"reason": "When the caller asks for available cars and wants to see pricing.",
"message": "Sure, I will send you our current price list via SMS right now.",
"content": "Price list for this week: https://example.com/prices",
"assistantId": "0195f1e2-0000-0000-0000-000000000000"
}'A successful update returns 200 with the reason as it now stands in data, updatedAt moved on. The shape is the same as the create response.
PUT replaces the whole record
PUT validates against the same schema as create, so all six fields are required; there
is no partial update on this resource and omitting a field is rejected rather than leaving
it unchanged.
That makes the read-modify-write pattern in the Node.js sample above the correct approach for any edit, not just a cautious one: fetch the reason, change what you need, send the complete record back.
Delete an SMS reason#
DELETE /sms-reasons/{id}
| Path param | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The SMS reason to delete. |
curl -X DELETE https://core-api.heysadie.ai/sms-reasons/0195f1e2-1111-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY"Delete returns 200 with the record it removed in data: the full reason, id and timestamps included, not an empty body. Capture it if you want a record of the rule you deleted without a separate read beforehand. That is the rule, not the messages it sent: for the texts that actually went out, see Conversations.
Errors#
Every failure uses the shared error envelope, with error.code as the value to branch on.
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | The body or query failed validation. error.details[] names each offending field and what is wrong with it. |
401 | UNAUTHORIZED | Missing, malformed, or invalid Authorization header. |
403 | FORBIDDEN | The key is valid but not allowed to act on this SMS reason. |
404 | RESOURCE_NOT_FOUND | No SMS reason with that id. |
500 | INTERNAL_SERVER_ERROR | Something went wrong on OnCore's side. |
400 is the one to read carefully on create and update: error.details is an array of { field, message } objects, one per invalid field, so it names what the API objected to rather than making you bisect the body. Since the spec does not publish the required-field set, this is how you discover it.
Rate limiting applies across the API rather than per resource; see Rate limits.
Next steps#
- SMS: answer inbound texts on the assistant's number. Separate from these mid-call rules.
- SMS Reasons API: the generated reference for these five endpoints, straight from the spec.
- Transfers: hand the caller to a person instead of sending a text.
- End-of-call report: the call record these texts are attached to.