# Archive a Contact DELETE https://api.smobi.com/v1/contacts/{identifier} Authentication: x-api-key header (brand-scoped API key). Archives the contact (soft delete): it is hidden from lists, excluded from campaign sends and segment syncs, and can be restored with PATCH {"active": true}. Idempotent — archiving an archived contact returns 204. Personal data can be cleared beforehand by PATCHing fields to null. ## Operation ```json { "description": "Archives the contact (soft delete): it is hidden from lists, excluded from campaign sends and segment syncs, and can be restored with PATCH {\"active\": true}. Idempotent — archiving an archived contact returns 204. Personal data can be cleared beforehand by PATCHing fields to null.", "operationId": "archiveContact", "parameters": [ { "name": "identifier", "required": true, "in": "path", "description": "Contact id (uuid) or phone number (digits-only recommended in URLs; E.164 requires encoding + as %2B)", "schema": { "example": "14155552222", "type": "string" } } ], "responses": { "204": { "description": "Contact archived" }, "400": { "description": "Identifier is neither a uuid nor a phone number" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } }, "404": { "description": "No contact with this id or phone number exists for the brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 404 }, "message": { "type": "string", "example": "Contact not found" }, "error": { "type": "string", "example": "Not Found" } } } } } } }, "summary": "Archive a Contact", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json {} ``` # Create or Update Contacts in Bulk POST https://api.smobi.com/v1/contacts/bulk Authentication: x-api-key header (brand-scoped API key). Upserts up to 100 contacts in one request. Items are processed independently: one failing item never aborts the batch — inspect per-item results. Duplicate phone numbers within a batch are rejected as a whole-request 400. ## Operation ```json { "description": "Upserts up to 100 contacts in one request. Items are processed independently: one failing item never aborts the batch — inspect per-item results. Duplicate phone numbers within a batch are rejected as a whole-request 400.", "operationId": "bulkUpsertContacts", "parameters": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkContactsDto" }, "examples": { "initial-import": { "summary": "Initial import of your user base", "description": "Chunk larger datasets into batches of 100; each request returns per-item results so you can retry only the failures.", "value": { "contacts": [ { "phoneNumber": "+14155552222", "firstName": "Jane", "tags": [ "plan-pro" ], "marketingConsent": "OPTED_IN" }, { "phoneNumber": "+14155552223", "firstName": "Sam", "customFields": { "externalId": "user_8232" } } ] } } } } } }, "responses": { "200": { "description": "Per-item results with summary counts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkContactsResponseDto" }, "example": { "results": [ { "index": 0, "phoneNumber": "+14155552222", "status": "created", "id": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, { "index": 1, "phoneNumber": "+14155552223", "status": "created", "id": "2c4d6e8f-1234-4567-89ab-123456789abc" } ], "summary": { "total": 2, "created": 2, "updated": 0, "failed": 0 } } } } }, "400": { "description": "Validation error (bad envelope, batch too large, or duplicate phone numbers)" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } } }, "summary": "Create or Update Contacts in Bulk", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "BulkContactsDto": { "type": "object", "properties": { "contacts": { "description": "1-100 contacts; duplicate phone numbers in one batch are rejected", "type": "array", "items": { "$ref": "#/components/schemas/CreateContactDto" }, "minItems": 1, "maxItems": 100 } }, "required": [ "contacts" ], "additionalProperties": false }, "CreateContactDto": { "type": "object", "properties": { "phoneNumber": { "type": "string", "example": "+14155552222", "description": "E.164 (+1234567890) or digits-only (1234567890) format" }, "firstName": { "type": "string", "example": "Jane", "nullable": true, "minLength": 1, "maxLength": 255 }, "lastName": { "type": "string", "example": "Doe", "nullable": true, "minLength": 1, "maxLength": 255 }, "email": { "type": "string", "example": "jane@example.com", "nullable": true, "format": "email", "maxLength": 255 }, "state": { "type": "string", "example": "CA", "nullable": true, "minLength": 1, "maxLength": 255 }, "country": { "type": "string", "example": "US", "nullable": true, "minLength": 1, "maxLength": 255 }, "dob": { "type": "string", "example": "1990-04-12", "description": "Date of birth in YYYY-MM-DD format. Must be a valid date in the past; null clears the value.", "nullable": true, "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "gender": { "type": "string", "nullable": true, "minLength": 1, "maxLength": 255 }, "preferredLanguage": { "type": "string", "example": "en", "nullable": true, "minLength": 1, "maxLength": 255 }, "tags": { "example": [ "vip" ], "description": "Full replace on updates; usable in segment rules", "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "maxItems": 100 }, "customFields": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string", "maxLength": 1000, "nullable": true }, { "type": "number" }, { "type": "boolean" }, { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 1000 } } ] }, "example": { "loyaltyTier": "gold" }, "description": "Map of up to 50 fields. Values may be strings (up to 1,000 characters), numbers, booleans, arrays of up to 100 strings, or null. Nested objects are not accepted. Keys must match ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$; `__proto__`, `constructor`, and `prototype` are reserved. A null value deletes that key on update.", "maxProperties": 50 }, "marketingConsent": { "type": "string", "enum": [ "OPTED_IN", "OPTED_OUT" ], "description": "Setting OPTED_IN attests that you collected the contact's marketing consent. OPTED_IN is rejected (422) when the contact previously opted out (e.g. texted STOP); OPTED_OUT is always accepted. Contacts default to UNKNOWN, and campaigns only send to OPTED_IN contacts." } }, "required": [ "phoneNumber" ], "additionalProperties": false }, "BulkContactsResponseDto": { "type": "object", "properties": { "results": { "type": "array", "items": { "$ref": "#/components/schemas/BulkContactItemResultDto" } }, "summary": { "$ref": "#/components/schemas/BulkContactsSummaryDto" } }, "required": [ "results", "summary" ] }, "BulkContactItemResultDto": { "type": "object", "properties": { "index": { "type": "number", "example": 0 }, "phoneNumber": { "type": "string", "example": "+14155552222" }, "status": { "type": "string", "enum": [ "created", "updated", "error" ] }, "id": { "type": "string", "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, "error": { "type": "string", "example": "Cannot opt in a contact that has opted out; the contact must opt back in themselves (e.g. by texting START)" } }, "required": [ "index", "phoneNumber", "status" ] }, "BulkContactsSummaryDto": { "type": "object", "properties": { "total": { "type": "number", "example": 3 }, "created": { "type": "number", "example": 2 }, "updated": { "type": "number", "example": 0 }, "failed": { "type": "number", "example": 1 } }, "required": [ "total", "created", "updated", "failed" ] } } ``` # Get a Contact GET https://api.smobi.com/v1/contacts/{identifier} Authentication: x-api-key header (brand-scoped API key). Fetch a single contact by Smobi id or phone number. ## Operation ```json { "description": "Fetch a single contact by Smobi id or phone number.", "operationId": "getContact", "parameters": [ { "name": "identifier", "required": true, "in": "path", "description": "Contact id (uuid) or phone number (digits-only recommended in URLs; E.164 requires encoding + as %2B)", "schema": { "example": "14155552222", "type": "string" } } ], "responses": { "200": { "description": "The contact", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactDto" } } } }, "400": { "description": "Identifier is neither a uuid nor a phone number" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } }, "404": { "description": "No contact with this id or phone number exists for the brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 404 }, "message": { "type": "string", "example": "Contact not found" }, "error": { "type": "string", "example": "Not Found" } } } } } } }, "summary": "Get a Contact", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "ContactDto": { "type": "object", "properties": { "id": { "type": "string", "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, "phoneNumber": { "type": "string", "example": "+14155552222", "description": "Always returned in E.164 format" }, "active": { "type": "boolean", "example": true, "description": "false when the contact has been archived (DELETE)" }, "firstName": { "type": "string", "nullable": true, "example": "Jane" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "email": { "type": "string", "nullable": true, "example": "jane@example.com" }, "state": { "type": "string", "nullable": true, "example": "CA" }, "country": { "type": "string", "nullable": true, "example": "US" }, "dob": { "type": "string", "nullable": true, "example": "1990-04-12", "description": "Date of birth (YYYY-MM-DD)" }, "gender": { "type": "string", "nullable": true, "example": null }, "preferredLanguage": { "type": "string", "nullable": true, "example": "en" }, "tags": { "example": [ "vip", "spring-sale" ], "type": "array", "items": { "type": "string" } }, "customFields": { "type": "object", "additionalProperties": true, "example": { "loyaltyTier": "gold", "storeIds": [ "s1", "s2" ] }, "description": "Stored custom-field values keyed by field name. See create/update request schemas for accepted write types and limits." }, "marketingConsent": { "$ref": "#/components/schemas/ContactMarketingConsentDto" }, "createdAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" }, "updatedAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "id", "phoneNumber", "active", "firstName", "lastName", "email", "state", "country", "dob", "gender", "preferredLanguage", "tags", "customFields", "marketingConsent", "createdAt", "updatedAt" ] }, "ContactMarketingConsentDto": { "type": "object", "properties": { "channel": { "type": "string", "enum": [ "MOBILE" ], "example": "MOBILE" }, "status": { "type": "string", "enum": [ "UNKNOWN", "SENT", "OPTED_IN", "OPTED_OUT" ], "example": "OPTED_IN", "description": "Campaigns are only sent to OPTED_IN contacts. Contacts created via the API start as UNKNOWN until consent is set." }, "updatedAt": { "type": "string", "nullable": true, "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "channel", "status", "updatedAt" ] } } ``` # List Contacts GET https://api.smobi.com/v1/contacts Authentication: x-api-key header (brand-scoped API key). Cursor-paginated list of the brand's contacts, newest first. Archived contacts are hidden by default (status=active). ## Operation ```json { "description": "Cursor-paginated list of the brand's contacts, newest first. Archived contacts are hidden by default (status=active).", "operationId": "listContacts", "parameters": [ { "name": "status", "required": false, "in": "query", "description": "Default: active", "schema": { "enum": [ "active", "archived", "all" ], "type": "string", "default": "active" } }, { "name": "tag", "required": false, "in": "query", "description": "Filter by tag; repeat the parameter to require multiple tags", "schema": { "oneOf": [ { "type": "string", "minLength": 1, "maxLength": 100 }, { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100 } } ] } }, { "name": "email", "required": false, "in": "query", "description": "Exact email lookup (case-insensitive)", "schema": { "type": "string", "format": "email" } }, { "name": "phoneNumber", "required": false, "in": "query", "description": "Exact phone lookup (E.164 or digits-only)", "schema": { "type": "string" } }, { "name": "cursor", "required": false, "in": "query", "description": "Opaque cursor from pagination.nextCursor", "schema": { "type": "string" } }, { "name": "limit", "required": false, "in": "query", "description": "1-100, default 50", "schema": { "example": 50, "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responses": { "200": { "description": "Page of contacts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactListResponseDto" } } } }, "400": { "description": "Invalid query parameter or cursor" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } } }, "summary": "List Contacts", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "ContactListResponseDto": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/ContactDto" } }, "pagination": { "$ref": "#/components/schemas/ContactListPaginationDto" } }, "required": [ "data", "pagination" ] }, "ContactDto": { "type": "object", "properties": { "id": { "type": "string", "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, "phoneNumber": { "type": "string", "example": "+14155552222", "description": "Always returned in E.164 format" }, "active": { "type": "boolean", "example": true, "description": "false when the contact has been archived (DELETE)" }, "firstName": { "type": "string", "nullable": true, "example": "Jane" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "email": { "type": "string", "nullable": true, "example": "jane@example.com" }, "state": { "type": "string", "nullable": true, "example": "CA" }, "country": { "type": "string", "nullable": true, "example": "US" }, "dob": { "type": "string", "nullable": true, "example": "1990-04-12", "description": "Date of birth (YYYY-MM-DD)" }, "gender": { "type": "string", "nullable": true, "example": null }, "preferredLanguage": { "type": "string", "nullable": true, "example": "en" }, "tags": { "example": [ "vip", "spring-sale" ], "type": "array", "items": { "type": "string" } }, "customFields": { "type": "object", "additionalProperties": true, "example": { "loyaltyTier": "gold", "storeIds": [ "s1", "s2" ] }, "description": "Stored custom-field values keyed by field name. See create/update request schemas for accepted write types and limits." }, "marketingConsent": { "$ref": "#/components/schemas/ContactMarketingConsentDto" }, "createdAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" }, "updatedAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "id", "phoneNumber", "active", "firstName", "lastName", "email", "state", "country", "dob", "gender", "preferredLanguage", "tags", "customFields", "marketingConsent", "createdAt", "updatedAt" ] }, "ContactMarketingConsentDto": { "type": "object", "properties": { "channel": { "type": "string", "enum": [ "MOBILE" ], "example": "MOBILE" }, "status": { "type": "string", "enum": [ "UNKNOWN", "SENT", "OPTED_IN", "OPTED_OUT" ], "example": "OPTED_IN", "description": "Campaigns are only sent to OPTED_IN contacts. Contacts created via the API start as UNKNOWN until consent is set." }, "updatedAt": { "type": "string", "nullable": true, "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "channel", "status", "updatedAt" ] }, "ContactListPaginationDto": { "type": "object", "properties": { "limit": { "type": "number", "example": 50 }, "hasMore": { "type": "boolean", "example": true }, "nextCursor": { "type": "string", "nullable": true, "example": "eyJjIjoiMjAyNi0wNy0wN1QxMjowMDowMC4wMDBaIiwiaSI6IjliMmYifQ", "description": "Opaque cursor for the next page; null on the last page" } }, "required": [ "limit", "hasMore", "nextCursor" ] } } ``` # Update a Contact PATCH https://api.smobi.com/v1/contacts/{identifier} Authentication: x-api-key header (brand-scoped API key). Partial update. Scalars: send a value to set it, null to clear it, omit to leave untouched. tags replaces the whole list; customFields merges per key (null deletes a key). phoneNumber is immutable. Set active=true to restore an archived contact. ## Operation ```json { "description": "Partial update. Scalars: send a value to set it, null to clear it, omit to leave untouched. tags replaces the whole list; customFields merges per key (null deletes a key). phoneNumber is immutable. Set active=true to restore an archived contact.", "operationId": "updateContact", "parameters": [ { "name": "identifier", "required": true, "in": "path", "description": "Contact id (uuid) or phone number (digits-only recommended in URLs; E.164 requires encoding + as %2B)", "schema": { "example": "14155552222", "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateContactDto" }, "examples": { "update-fields": { "summary": "Update attributes", "description": "tags replaces the whole list; customFields merges per key.", "value": { "tags": [ "plan-enterprise" ], "customFields": { "seats": 25 } } }, "clear-data": { "summary": "Clear personal data", "description": "null clears a scalar field or deletes a customFields key — use before archiving to remove personal data.", "value": { "firstName": null, "lastName": null, "email": null, "customFields": { "externalId": null } } }, "opt-out": { "summary": "Suppress marketing", "value": { "marketingConsent": "OPTED_OUT" } }, "restore": { "summary": "Restore an archived contact", "value": { "active": true } } } } } }, "responses": { "200": { "description": "The updated contact", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactDto" } } } }, "400": { "description": "Validation error, empty body, or phoneNumber in body" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } }, "404": { "description": "No contact with this id or phone number exists for the brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 404 }, "message": { "type": "string", "example": "Contact not found" }, "error": { "type": "string", "example": "Not Found" } } } } } }, "422": { "description": "Consent transition rejected: a contact that opted out (e.g. texted STOP) cannot be opted back in via the API", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 422 }, "message": { "type": "string", "example": "Cannot opt in a contact that has opted out; the contact must opt back in themselves (e.g. by texting START)" }, "error": { "type": "string", "example": "Unprocessable Entity" } } } } } } }, "summary": "Update a Contact", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "UpdateContactDto": { "type": "object", "properties": { "firstName": { "type": "string", "example": "Jane", "nullable": true, "minLength": 1, "maxLength": 255 }, "lastName": { "type": "string", "example": "Doe", "nullable": true, "minLength": 1, "maxLength": 255 }, "email": { "type": "string", "example": "jane@example.com", "nullable": true, "format": "email", "maxLength": 255 }, "state": { "type": "string", "example": "CA", "nullable": true, "minLength": 1, "maxLength": 255 }, "country": { "type": "string", "example": "US", "nullable": true, "minLength": 1, "maxLength": 255 }, "dob": { "type": "string", "example": "1990-04-12", "description": "Date of birth in YYYY-MM-DD format. Must be a valid date in the past; null clears the value.", "nullable": true, "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "gender": { "type": "string", "nullable": true, "minLength": 1, "maxLength": 255 }, "preferredLanguage": { "type": "string", "example": "en", "nullable": true, "minLength": 1, "maxLength": 255 }, "tags": { "example": [ "vip" ], "description": "Full replace on updates; usable in segment rules", "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "maxItems": 100 }, "customFields": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string", "maxLength": 1000, "nullable": true }, { "type": "number" }, { "type": "boolean" }, { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 1000 } } ] }, "example": { "loyaltyTier": "gold" }, "description": "Map of up to 50 fields. Values may be strings (up to 1,000 characters), numbers, booleans, arrays of up to 100 strings, or null. Nested objects are not accepted. Keys must match ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$; `__proto__`, `constructor`, and `prototype` are reserved. A null value deletes that key on update.", "maxProperties": 50 }, "marketingConsent": { "type": "string", "enum": [ "OPTED_IN", "OPTED_OUT" ], "description": "Setting OPTED_IN attests that you collected the contact's marketing consent. OPTED_IN is rejected (422) when the contact previously opted out (e.g. texted STOP); OPTED_OUT is always accepted. Contacts default to UNKNOWN, and campaigns only send to OPTED_IN contacts." }, "active": { "type": "boolean", "example": true, "description": "Set true to restore an archived contact" } }, "additionalProperties": false, "minProperties": 1 }, "ContactDto": { "type": "object", "properties": { "id": { "type": "string", "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, "phoneNumber": { "type": "string", "example": "+14155552222", "description": "Always returned in E.164 format" }, "active": { "type": "boolean", "example": true, "description": "false when the contact has been archived (DELETE)" }, "firstName": { "type": "string", "nullable": true, "example": "Jane" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "email": { "type": "string", "nullable": true, "example": "jane@example.com" }, "state": { "type": "string", "nullable": true, "example": "CA" }, "country": { "type": "string", "nullable": true, "example": "US" }, "dob": { "type": "string", "nullable": true, "example": "1990-04-12", "description": "Date of birth (YYYY-MM-DD)" }, "gender": { "type": "string", "nullable": true, "example": null }, "preferredLanguage": { "type": "string", "nullable": true, "example": "en" }, "tags": { "example": [ "vip", "spring-sale" ], "type": "array", "items": { "type": "string" } }, "customFields": { "type": "object", "additionalProperties": true, "example": { "loyaltyTier": "gold", "storeIds": [ "s1", "s2" ] }, "description": "Stored custom-field values keyed by field name. See create/update request schemas for accepted write types and limits." }, "marketingConsent": { "$ref": "#/components/schemas/ContactMarketingConsentDto" }, "createdAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" }, "updatedAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "id", "phoneNumber", "active", "firstName", "lastName", "email", "state", "country", "dob", "gender", "preferredLanguage", "tags", "customFields", "marketingConsent", "createdAt", "updatedAt" ] }, "ContactMarketingConsentDto": { "type": "object", "properties": { "channel": { "type": "string", "enum": [ "MOBILE" ], "example": "MOBILE" }, "status": { "type": "string", "enum": [ "UNKNOWN", "SENT", "OPTED_IN", "OPTED_OUT" ], "example": "OPTED_IN", "description": "Campaigns are only sent to OPTED_IN contacts. Contacts created via the API start as UNKNOWN until consent is set." }, "updatedAt": { "type": "string", "nullable": true, "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "channel", "status", "updatedAt" ] } } ``` # Create or Update a Contact POST https://api.smobi.com/v1/contacts Authentication: x-api-key header (brand-scoped API key). Upserts by phone number: creates the contact if the phone number is new to your brand (201), otherwise applies the provided fields to the existing contact (200). Retries are safe. New contacts start with marketingConsent UNKNOWN — campaigns only send to OPTED_IN contacts, so set marketingConsent once you have collected consent. ## Operation ```json { "description": "Upserts by phone number: creates the contact if the phone number is new to your brand (201), otherwise applies the provided fields to the existing contact (200). Retries are safe. New contacts start with marketingConsent UNKNOWN — campaigns only send to OPTED_IN contacts, so set marketingConsent once you have collected consent.", "operationId": "upsertContact", "parameters": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateContactDto" }, "examples": { "minimal": { "summary": "Minimal — phone only", "description": "Creates the contact with marketingConsent UNKNOWN; set consent later once collected.", "value": { "phoneNumber": "+14155552222" } }, "signup-sync": { "summary": "Sync a new signup with your attributes", "description": "tags and customFields are usable in segment rules — store plans, cohorts, or your own user id here.", "value": { "phoneNumber": "+14155552222", "firstName": "Jane", "lastName": "Doe", "email": "jane@example.com", "tags": [ "beta", "plan-pro" ], "customFields": { "externalId": "user_8231", "signupSource": "ios" } } }, "with-consent": { "summary": "Create with collected consent", "description": "Setting OPTED_IN attests that you collected the contact's marketing consent — required before campaigns will send to them.", "value": { "phoneNumber": "+14155552222", "firstName": "Jane", "marketingConsent": "OPTED_IN" } } } } } }, "responses": { "200": { "description": "Existing contact updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactDto" } } } }, "201": { "description": "Contact created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactDto" } } } }, "400": { "description": "Validation error" }, "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 401 }, "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" } } } } } }, "403": { "description": "The API key is not scoped to a brand", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 403 }, "message": { "type": "string", "example": "This endpoint requires a brand-scoped API key" }, "error": { "type": "string", "example": "Forbidden" } } } } } }, "422": { "description": "Consent transition rejected: a contact that opted out (e.g. texted STOP) cannot be opted back in via the API", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 422 }, "message": { "type": "string", "example": "Cannot opt in a contact that has opted out; the contact must opt back in themselves (e.g. by texting START)" }, "error": { "type": "string", "example": "Unprocessable Entity" } } } } } } }, "summary": "Create or Update a Contact", "tags": [ "Contacts" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "CreateContactDto": { "type": "object", "properties": { "phoneNumber": { "type": "string", "example": "+14155552222", "description": "E.164 (+1234567890) or digits-only (1234567890) format" }, "firstName": { "type": "string", "example": "Jane", "nullable": true, "minLength": 1, "maxLength": 255 }, "lastName": { "type": "string", "example": "Doe", "nullable": true, "minLength": 1, "maxLength": 255 }, "email": { "type": "string", "example": "jane@example.com", "nullable": true, "format": "email", "maxLength": 255 }, "state": { "type": "string", "example": "CA", "nullable": true, "minLength": 1, "maxLength": 255 }, "country": { "type": "string", "example": "US", "nullable": true, "minLength": 1, "maxLength": 255 }, "dob": { "type": "string", "example": "1990-04-12", "description": "Date of birth in YYYY-MM-DD format. Must be a valid date in the past; null clears the value.", "nullable": true, "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "gender": { "type": "string", "nullable": true, "minLength": 1, "maxLength": 255 }, "preferredLanguage": { "type": "string", "example": "en", "nullable": true, "minLength": 1, "maxLength": 255 }, "tags": { "example": [ "vip" ], "description": "Full replace on updates; usable in segment rules", "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "maxItems": 100 }, "customFields": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string", "maxLength": 1000, "nullable": true }, { "type": "number" }, { "type": "boolean" }, { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 1000 } } ] }, "example": { "loyaltyTier": "gold" }, "description": "Map of up to 50 fields. Values may be strings (up to 1,000 characters), numbers, booleans, arrays of up to 100 strings, or null. Nested objects are not accepted. Keys must match ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$; `__proto__`, `constructor`, and `prototype` are reserved. A null value deletes that key on update.", "maxProperties": 50 }, "marketingConsent": { "type": "string", "enum": [ "OPTED_IN", "OPTED_OUT" ], "description": "Setting OPTED_IN attests that you collected the contact's marketing consent. OPTED_IN is rejected (422) when the contact previously opted out (e.g. texted STOP); OPTED_OUT is always accepted. Contacts default to UNKNOWN, and campaigns only send to OPTED_IN contacts." } }, "required": [ "phoneNumber" ], "additionalProperties": false }, "ContactDto": { "type": "object", "properties": { "id": { "type": "string", "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e" }, "phoneNumber": { "type": "string", "example": "+14155552222", "description": "Always returned in E.164 format" }, "active": { "type": "boolean", "example": true, "description": "false when the contact has been archived (DELETE)" }, "firstName": { "type": "string", "nullable": true, "example": "Jane" }, "lastName": { "type": "string", "nullable": true, "example": "Doe" }, "email": { "type": "string", "nullable": true, "example": "jane@example.com" }, "state": { "type": "string", "nullable": true, "example": "CA" }, "country": { "type": "string", "nullable": true, "example": "US" }, "dob": { "type": "string", "nullable": true, "example": "1990-04-12", "description": "Date of birth (YYYY-MM-DD)" }, "gender": { "type": "string", "nullable": true, "example": null }, "preferredLanguage": { "type": "string", "nullable": true, "example": "en" }, "tags": { "example": [ "vip", "spring-sale" ], "type": "array", "items": { "type": "string" } }, "customFields": { "type": "object", "additionalProperties": true, "example": { "loyaltyTier": "gold", "storeIds": [ "s1", "s2" ] }, "description": "Stored custom-field values keyed by field name. See create/update request schemas for accepted write types and limits." }, "marketingConsent": { "$ref": "#/components/schemas/ContactMarketingConsentDto" }, "createdAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" }, "updatedAt": { "type": "string", "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "id", "phoneNumber", "active", "firstName", "lastName", "email", "state", "country", "dob", "gender", "preferredLanguage", "tags", "customFields", "marketingConsent", "createdAt", "updatedAt" ] }, "ContactMarketingConsentDto": { "type": "object", "properties": { "channel": { "type": "string", "enum": [ "MOBILE" ], "example": "MOBILE" }, "status": { "type": "string", "enum": [ "UNKNOWN", "SENT", "OPTED_IN", "OPTED_OUT" ], "example": "OPTED_IN", "description": "Campaigns are only sent to OPTED_IN contacts. Contacts created via the API start as UNKNOWN until consent is set." }, "updatedAt": { "type": "string", "nullable": true, "example": "2026-07-07T12:00:00.000Z" } }, "required": [ "channel", "status", "updatedAt" ] } } ``` # API Reference (/docs/api) Base URL: `https://api.smobi.com/v1`. Every request requires an `x-api-key` header. [Download OpenAPI](/openapi.json) · [Read the Quickstart](/docs/quickstart) - [Send a Message](/docs/api/messaging/send-message) — `POST /message` - [Check Channel Support](/docs/api/messaging/check-channel-support) — `POST /check-channel-support/{phoneNumber}` - [Execute a Workflow](/docs/api/workflows/execute-single) — `POST /workflows/{workflowDefinitionId}/execute/{phoneNumber}` - [Execute the Current Workflow Version](/docs/api/workflows/execute-family) — `POST /workflows/family/{workflowFamilyId}/execute/{phoneNumber}` - [Execute a Workflow Batch](/docs/api/workflows/execute-batch) — `POST /workflows/{workflowDefinitionId}/execute` - [List Contacts](/docs/api/contacts/list-contacts) — `GET /contacts` - [Create or Update a Contact](/docs/api/contacts/upsert-contact) — `POST /contacts` - [Create or Update Contacts in Bulk](/docs/api/contacts/bulk-upsert-contacts) — `POST /contacts/bulk` - [Get a Contact](/docs/api/contacts/get-contact) — `GET /contacts/{identifier}` - [Update a Contact](/docs/api/contacts/update-contact) — `PATCH /contacts/{identifier}` - [Archive a Contact](/docs/api/contacts/archive-contact) — `DELETE /contacts/{identifier}` Outbound [webhook payloads](/docs/guides/webhooks) describe requests sent to your server, not endpoints on api.smobi.com. # Check Channel Support POST https://api.smobi.com/v1/check-channel-support/{phoneNumber} Authentication: x-api-key header (brand-scoped API key). Check whether a phone number supports RCS or SMS before choosing a channel. Requires a configured RCS agent. Creates or updates the customer capability record and records API usage. ## Operation ```json { "operationId": "checkChannelSupport", "parameters": [ { "name": "phoneNumber", "required": true, "in": "path", "description": "Phone number to check channel support for", "schema": { "type": "string" } } ], "responses": { "201": { "description": "Channel support information retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckChannelCapabilityResponse" }, "examples": { "check-channel-support": { "summary": "Check Channel Support", "description": "Example of checking which channels are supported for a phone number", "value": { "rcs": false, "sms": true } } } } } }, "400": { "description": "Bad Request - Invalid phone number", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid phone number" }, "error": { "type": "string", "example": "Bad Request" }, "statusCode": { "type": "number", "example": 400 } } } } } }, "401": { "description": "Unauthorized - Invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" }, "statusCode": { "type": "number", "example": 401 } } } } } } }, "summary": "Check Channel Support", "tags": [ "Messaging" ], "security": [ { "x-api-key": [] } ], "description": "Check whether a phone number supports RCS or SMS before choosing a channel. Requires a configured RCS agent. Creates or updates the customer capability record and records API usage." } ``` ## Referenced Schemas ```json { "CheckChannelCapabilityResponse": { "type": "object", "properties": { "rcs": { "type": "boolean", "description": "Whether the channel supports RCS", "example": true }, "sms": { "type": "boolean", "description": "Whether the channel supports SMS", "example": true } }, "required": [ "rcs", "sms" ] } } ``` # Send a Message POST https://api.smobi.com/v1/message Authentication: x-api-key header (brand-scoped API key). Send an SMS or RCS message to a customer. Include SMS content alongside RCS to provide a fallback when RCS is unavailable. ## Operation ```json { "operationId": "sendMessage", "parameters": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendMessage" }, "examples": { "sms-text": { "summary": "SMS Text", "value": { "clientRef": "order-123", "recipientPhoneNumber": "15552223333", "content": { "sms": { "content": "Your order is on its way." } } } }, "rcs-text": { "summary": "RCS Text with SMS Fallback", "value": { "clientRef": "order-123", "recipientPhoneNumber": "15552223333", "content": { "rcs": { "content": { "type": "TEXT", "content": "Your order is on its way." } }, "sms": { "content": "Your order is on its way." } } } }, "rich-card": { "summary": "Rich Card", "value": { "clientRef": "order-123", "recipientPhoneNumber": "15552223333", "content": { "rcs": { "content": { "type": "RICH_CARD", "item": { "title": "Your Order Is on Its Way", "description": "Track your delivery for the latest updates." } } } } } }, "send-rich-card": { "summary": "Send Rich Card Message", "description": "Example of sending a rich card message with both RCS and SMS fallback", "value": { "clientRef": "61efac14-961c-4070-a369-04e16578e86f", "recipientPhoneNumber": "15552223333", "content": { "rcs": { "content": { "type": "RICH_CARD", "item": { "title": "Blue Cotton T-Shirt", "description": "Premium comfort with style. Available in S-XL sizes.", "mediaUrl": "https://smobi-public-templates.s3.us-east-1.amazonaws.com/public/products/Product7.png", "orientation": "VERTICAL", "thumbnailPosition": "RIGHT", "height": "SHORT", "localSuggestions": [] }, "globalSuggestions": [ { "id": "reply-1", "text": "Reply to message", "type": "REPLY" }, { "id": "url-1", "text": "Visit our website", "type": "OPEN_URL", "url": "https://example.com" }, { "id": "phone-1", "text": "Call support", "type": "DIAL_PHONE", "phoneNumber": "+12223334444" }, { "id": "location-1", "text": "Show location", "type": "SHOW_LOCATION", "latitude": "37.7749", "longitude": "-122.4194", "label": "San Francisco" } ] } }, "sms": { "content": "Check out our Blue Cotton T-Shirt! View it here: https://example.com/products/blue-tshirt. Need help? Reply to this message.", "mediaUrl": "https://smobi-public-templates.s3.us-east-1.amazonaws.com/public/products/Product7.png" } } } } } } } }, "responses": { "201": { "description": "Message accepted for processing. Delivery is reported asynchronously through status webhooks.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendMessageResponse" } } } }, "400": { "description": "Bad Request - Invalid request parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid phone number" }, "error": { "type": "string", "example": "Bad Request" }, "statusCode": { "type": "number", "example": 400 } } } } } }, "401": { "description": "Unauthorized - Invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" }, "statusCode": { "type": "number", "example": 401 } } } } } } }, "summary": "Send a Message", "tags": [ "Messaging" ], "security": [ { "x-api-key": [] } ], "description": "Send an SMS or RCS message to a customer. Include SMS content alongside RCS to provide a fallback when RCS is unavailable." } ``` ## Referenced Schemas ```json { "SendMessage": { "type": "object", "properties": { "clientRef": { "type": "string", "description": "Client reference ID", "example": "client-ref-123" }, "recipientPhoneNumber": { "type": "string", "description": "Recipient phone number, string of all digits, for example, 12223334444", "example": "15552223333" }, "content": { "description": "Message content", "example": { "rcs": { "content": { "type": "TEXT", "content": "Hello, world!", "globalSuggestions": [ { "type": "REPLY", "text": "Reply", "id": "reply-1" } ] } }, "sms": { "content": "Check out this image!", "mediaUrl": "https://example.com/image.jpg" } }, "allOf": [ { "$ref": "#/components/schemas/RenderedTemplateContent" } ] } }, "required": [ "clientRef", "recipientPhoneNumber", "content" ] }, "RenderedTemplateContent": { "type": "object", "properties": { "rcs": { "description": "Rendered RCS content, used if RCS is supported by the recipientPhoneNumber.", "allOf": [ { "$ref": "#/components/schemas/RcsContent" } ] }, "sms": { "description": "SMS text or media, also used as an RCS fallback. Provide non-empty text, a media URL, or both. Select a variant to inspect its required fields.", "allOf": [ { "$ref": "#/components/schemas/SmsContent" } ] } }, "required": [], "additionalProperties": false, "description": "Message content for RCS, SMS, or both. Supply content for at least one configured channel. RCS is not required for SMS-only messages." }, "RcsContent": { "type": "object", "properties": { "content": { "description": "Rendered RCS message content", "oneOf": [ { "$ref": "#/components/schemas/TextMessage" }, { "$ref": "#/components/schemas/ContentMessage" }, { "$ref": "#/components/schemas/RichCardMessage" }, { "$ref": "#/components/schemas/CarouselMessage" } ] } }, "required": [ "content" ] }, "TextMessage": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "TEXT" ], "description": "Type of message" }, "globalSuggestions": { "type": "array", "description": "Global suggestions associated with the message. Limit 11 suggestions, max 25 characters each.", "items": { "oneOf": [ { "$ref": "#/components/schemas/ReplyAction" }, { "$ref": "#/components/schemas/OpenUrlAction" }, { "$ref": "#/components/schemas/DialPhoneAction" }, { "$ref": "#/components/schemas/ShowLocationAction" }, { "$ref": "#/components/schemas/RequestLocationAction" }, { "$ref": "#/components/schemas/CalendarEventAction" } ], "discriminator": { "propertyName": "type", "mapping": { "REPLY": "#/components/schemas/ReplyAction", "OPEN_URL": "#/components/schemas/OpenUrlAction", "DIAL_PHONE": "#/components/schemas/DialPhoneAction", "SHOW_LOCATION": "#/components/schemas/ShowLocationAction", "REQUEST_LOCATION": "#/components/schemas/RequestLocationAction", "CALENDAR_EVENT": "#/components/schemas/CalendarEventAction" } } }, "maxItems": 11 }, "content": { "type": "string", "description": "Text content of the message", "minLength": 1 } }, "required": [ "type", "content" ] }, "ReplyAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "REPLY" ], "description": "Type of action" } }, "required": [ "id", "text", "type" ] }, "OpenUrlAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "OPEN_URL" ], "description": "Type of action" }, "url": { "type": "string", "description": "The publicly accessible URL to open, for example, https://www.example.com" } }, "required": [ "id", "text", "type", "url" ] }, "DialPhoneAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "DIAL_PHONE" ], "description": "Type of action" }, "phoneNumber": { "type": "string", "description": "The phone number in E.164 format, for example, +12223334444" } }, "required": [ "id", "text", "type", "phoneNumber" ] }, "ShowLocationAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "SHOW_LOCATION" ], "description": "Type of action" }, "latitude": { "type": "string", "description": "The latitude in degrees. It must be in the range [-90.0, +90.0]." }, "longitude": { "type": "string", "description": "The longitude in degrees. It must be in the range [-180.0, +180.0]." }, "label": { "type": "string", "description": "The label of the location, for example, \"Home\", \"Work\", \"School\", etc." } }, "required": [ "id", "text", "type", "latitude", "longitude" ] }, "RequestLocationAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "REQUEST_LOCATION" ], "description": "Type of action" } }, "required": [ "id", "text", "type" ] }, "CalendarEventAction": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier for the action", "minLength": 1 }, "text": { "type": "string", "description": "Text to display for the action, Maximum 25 characters", "minLength": 1, "maxLength": 25 }, "type": { "type": "string", "enum": [ "CALENDAR_EVENT" ], "description": "Type of action" }, "eventTitle": { "type": "string", "description": "Event title. Maximum 100 characters" }, "eventDescription": { "type": "string", "description": "Event description. Maximum 500 characters" }, "startDate": { "format": "date-time", "type": "string", "description": "The start date of the event, in RFC 3339 format" }, "endDate": { "format": "date-time", "type": "string", "description": "The end date of the event, in RFC 3339 format" } }, "required": [ "id", "text", "type", "eventTitle", "startDate", "endDate" ] }, "ContentMessage": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "CONTENT" ], "description": "Type of message" }, "globalSuggestions": { "type": "array", "description": "Global suggestions associated with the message. Limit 11 suggestions, max 25 characters each.", "items": { "oneOf": [ { "$ref": "#/components/schemas/ReplyAction" }, { "$ref": "#/components/schemas/OpenUrlAction" }, { "$ref": "#/components/schemas/DialPhoneAction" }, { "$ref": "#/components/schemas/ShowLocationAction" }, { "$ref": "#/components/schemas/RequestLocationAction" }, { "$ref": "#/components/schemas/CalendarEventAction" } ], "discriminator": { "propertyName": "type", "mapping": { "REPLY": "#/components/schemas/ReplyAction", "OPEN_URL": "#/components/schemas/OpenUrlAction", "DIAL_PHONE": "#/components/schemas/DialPhoneAction", "SHOW_LOCATION": "#/components/schemas/ShowLocationAction", "REQUEST_LOCATION": "#/components/schemas/RequestLocationAction", "CALENDAR_EVENT": "#/components/schemas/CalendarEventAction" } } }, "maxItems": 11 }, "contentUrl": { "type": "string", "description": "The publicly accessible URL of the file, for example, https://www.example.com/content.jpg", "minLength": 1 } }, "required": [ "type", "contentUrl" ] }, "RichCardMessage": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "RICH_CARD" ], "description": "Type of message" }, "globalSuggestions": { "type": "array", "description": "Global suggestions associated with the message. Limit 11 suggestions, max 25 characters each.", "items": { "oneOf": [ { "$ref": "#/components/schemas/ReplyAction" }, { "$ref": "#/components/schemas/OpenUrlAction" }, { "$ref": "#/components/schemas/DialPhoneAction" }, { "$ref": "#/components/schemas/ShowLocationAction" }, { "$ref": "#/components/schemas/RequestLocationAction" }, { "$ref": "#/components/schemas/CalendarEventAction" } ], "discriminator": { "propertyName": "type", "mapping": { "REPLY": "#/components/schemas/ReplyAction", "OPEN_URL": "#/components/schemas/OpenUrlAction", "DIAL_PHONE": "#/components/schemas/DialPhoneAction", "SHOW_LOCATION": "#/components/schemas/ShowLocationAction", "REQUEST_LOCATION": "#/components/schemas/RequestLocationAction", "CALENDAR_EVENT": "#/components/schemas/CalendarEventAction" } } }, "maxItems": 11 }, "item": { "description": "Rich card content", "allOf": [ { "$ref": "#/components/schemas/RichCardBaseMessage" } ] } }, "required": [ "type", "item" ] }, "RichCardBaseMessage": { "type": "object", "properties": { "title": { "type": "string", "description": "Title of the rich card, Maximum 200 characters", "minLength": 1 }, "description": { "type": "string", "description": "Description of the rich card, Maximum 2000 characters" }, "mediaUrl": { "type": "string", "description": "The publicly accessible URL of the image attachment. Supported types are .jpg, .jpeg, and .png" }, "orientation": { "type": "string", "enum": [ "VERTICAL", "HORIZONTAL" ], "description": "Orientation of the rich card" }, "thumbnailPosition": { "type": "string", "enum": [ "LEFT", "RIGHT" ], "description": "Position of the thumbnail" }, "height": { "type": "string", "enum": [ "SHORT", "MEDIUM", "TALL" ], "description": "Height of the rich card" }, "localSuggestions": { "type": "array", "description": "Local suggestions associated with the rich card. Limit 4 suggestions, max 25 characters each.", "items": { "oneOf": [ { "$ref": "#/components/schemas/ReplyAction" }, { "$ref": "#/components/schemas/OpenUrlAction" }, { "$ref": "#/components/schemas/DialPhoneAction" }, { "$ref": "#/components/schemas/ShowLocationAction" }, { "$ref": "#/components/schemas/RequestLocationAction" }, { "$ref": "#/components/schemas/CalendarEventAction" } ], "discriminator": { "propertyName": "type", "mapping": { "REPLY": "#/components/schemas/ReplyAction", "OPEN_URL": "#/components/schemas/OpenUrlAction", "DIAL_PHONE": "#/components/schemas/DialPhoneAction", "SHOW_LOCATION": "#/components/schemas/ShowLocationAction", "REQUEST_LOCATION": "#/components/schemas/RequestLocationAction", "CALENDAR_EVENT": "#/components/schemas/CalendarEventAction" } } }, "maxItems": 4 } }, "required": [ "title" ] }, "CarouselMessage": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "CAROUSEL" ], "description": "Type of message" }, "globalSuggestions": { "type": "array", "description": "Global suggestions associated with the message. Limit 11 suggestions, max 25 characters each.", "items": { "oneOf": [ { "$ref": "#/components/schemas/ReplyAction" }, { "$ref": "#/components/schemas/OpenUrlAction" }, { "$ref": "#/components/schemas/DialPhoneAction" }, { "$ref": "#/components/schemas/ShowLocationAction" }, { "$ref": "#/components/schemas/RequestLocationAction" }, { "$ref": "#/components/schemas/CalendarEventAction" } ], "discriminator": { "propertyName": "type", "mapping": { "REPLY": "#/components/schemas/ReplyAction", "OPEN_URL": "#/components/schemas/OpenUrlAction", "DIAL_PHONE": "#/components/schemas/DialPhoneAction", "SHOW_LOCATION": "#/components/schemas/ShowLocationAction", "REQUEST_LOCATION": "#/components/schemas/RequestLocationAction", "CALENDAR_EVENT": "#/components/schemas/CalendarEventAction" } } }, "maxItems": 11 }, "width": { "type": "string", "enum": [ "SMALL", "MEDIUM" ], "description": "Width of the carousel" }, "items": { "type": "array", "description": "Items in the carousel", "items": { "$ref": "#/components/schemas/RichCardBaseMessage" }, "minItems": 2, "maxItems": 10 } }, "required": [ "type", "width", "items" ] }, "SmsContent": { "type": "object", "properties": { "content": { "type": "string", "description": "Content of the SMS message. Optional if mediaUrl is provided (content becomes caption).", "example": "Hello, this is your message text", "minLength": 1 }, "mediaUrl": { "type": "string", "description": "Optional media URL. When provided, content becomes the caption for the media.", "example": "https://example.com/image.jpg" } }, "description": "Provide non-empty text content, a media URL, or both. When media is supplied, text is the caption.", "anyOf": [ { "title": "SMS Text", "required": [ "content" ] }, { "title": "SMS Media", "required": [ "mediaUrl" ], "properties": { "mediaUrl": { "minLength": 1 } } } ] }, "SendMessageResponse": { "type": "object", "properties": { "status": { "type": "string", "description": "Status of the message processing", "example": "PROCESSING" }, "smobiMessageId": { "type": "string", "description": "Unique identifier for the message in Smobi system", "example": "123e4567-e89b-12d3-a456-426614174000" } }, "required": [ "status", "smobiMessageId" ] } } ``` # Execute a Workflow Batch POST https://api.smobi.com/v1/workflows/{workflowDefinitionId}/execute Authentication: x-api-key header (brand-scoped API key). Triggers workflow execution for a batch of customers. Maximum 25 executions per request. For larger datasets, split into multiple requests. ## Operation ```json { "description": "Triggers workflow execution for a batch of customers. \n Maximum 25 executions per request.\n For larger datasets, split into multiple requests.", "operationId": "executeBatch", "parameters": [ { "name": "workflowDefinitionId", "required": true, "in": "path", "description": "Workflow definition identifier to execute", "schema": { "example": "wf_123e4567-e89b-12d3-a456-426614174000", "type": "string" } } ], "requestBody": { "required": true, "description": "Workflow execution batch request", "content": { "application/json": { "schema": { "type": "object", "required": [ "executions" ], "properties": { "executions": { "type": "array", "description": "One to 25 executions. Duplicate phone-number strings in the same batch are rejected.", "minItems": 1, "maxItems": 25, "items": { "type": "object", "required": [ "phoneNumber" ], "properties": { "phoneNumber": { "type": "string", "description": "Phone number in E.164 format (+1234567890) or digits-only format (1234567890)", "pattern": "^(^\\+[1-9]\\d{1,14}$|^[1-9]\\d{1,14}$)$", "examples": [ "+14155552222", "14155552222" ] }, "variables": { "type": "object", "description": "Variables for the workflow execution", "additionalProperties": { "type": "string" }, "example": { "customer.name": "John Doe", "customer.phone": "15552223333", "order.id": "12345" } }, "settings": { "type": "object", "description": "Accepted by request validation, but replaced with server-selected routing before the batch is queued. Omit this object. channelOverride is stripped and does not take effect for batches; use single execution for a supported override.", "properties": { "channel": { "type": "string", "enum": [ "SMS", "RCS", "MMS" ], "description": "Messaging channel." }, "provider": { "type": "string", "enum": [ "VONAGE", "VIBES", "TWILIO" ], "description": "Messaging provider." }, "agentId": { "type": "string", "description": "Agent identifier." }, "agentReferenceId": { "type": "string", "description": "Agent reference identifier." } } } } }, "example": [ { "phoneNumber": "14155552222", "variables": { "customer.name": "John Doe", "customer.phone": "15552223333", "order.id": "12345" } } ] } } }, "examples": { "withoutVariables": { "summary": "Without variables", "description": "Execute workflow for multiple customers", "value": { "executions": [ { "phoneNumber": "15552223333" }, { "phoneNumber": "15552223334" } ] } }, "withVariables": { "summary": "With variables", "description": "Execute workflow for multiple customers with variables", "value": { "executions": [ { "phoneNumber": "15552223333", "variables": { "customerName": "John Doe", "orderId": "12345", "discountCode": "SAVE20" } }, { "phoneNumber": "15552223334", "variables": { "customerName": "Jane Smith", "orderId": "67890", "discountCode": "SAVE30" } } ] } } } } } }, "responses": { "202": { "description": "Batch accepted for processing", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteWorkflowBatchResponse" } } } }, "400": { "description": "Invalid request - batch size exceeded or validation error", "content": { "application/json": { "schema": { "type": "object", "properties": { "statusCode": { "type": "number", "example": 400 }, "message": { "type": "string", "example": "Batch exceeds maximum allowed 25 executions. Split your request into smaller batches." }, "error": { "type": "string", "example": "Bad Request" }, "suggestion": { "type": "string", "example": "Split your request into smaller batches or use our bulk import API for large datasets." } } } } } }, "401": { "description": "Unauthorized - Invalid API key or missing authentication", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" }, "statusCode": { "type": "number", "example": 401 } } } } } }, "403": { "description": "Forbidden - Insufficient permissions to access the workflow", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Access denied to workflow" }, "error": { "type": "string", "example": "Forbidden" }, "statusCode": { "type": "number", "example": 403 } } } } } }, "404": { "description": "Not Found - Workflow not found or does not exist", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Workflow not found" }, "error": { "type": "string", "example": "Not Found" }, "statusCode": { "type": "number", "example": 404 } } } } } }, "422": { "description": "Unprocessable Entity - Workflow validation errors, circular dependencies, or invalid variable references", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Workflow validation failed" }, "error": { "type": "string", "example": "Unprocessable Entity" }, "statusCode": { "type": "number", "example": 422 } } } } } }, "500": { "description": "Internal Server Error - Unexpected server error during workflow execution", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Internal server error" }, "error": { "type": "string", "example": "Internal Server Error" }, "statusCode": { "type": "number", "example": 500 } } } } } } }, "summary": "Execute a Workflow Batch", "tags": [ "Workflows" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "ExecuteWorkflowBatchResponse": { "type": "object", "properties": { "success": { "type": "boolean", "description": "Whether the batch was successfully queued", "example": true }, "executions": { "description": "List of executions with their generated IDs", "example": [ { "phoneNumber": "15552223333", "executionId": "123e4567-e89b-12d3-a456-426614174000" }, { "phoneNumber": "15552223334", "executionId": "987fcdeb-51a2-43d7-8f9e-123456789abc" } ], "type": "array", "items": { "$ref": "#/components/schemas/BatchExecutionResponseItem" } } }, "required": [ "success", "executions" ] }, "BatchExecutionResponseItem": { "type": "object", "properties": { "phoneNumber": { "type": "string", "description": "Phone number for this execution", "example": "15552223333" }, "executionId": { "type": "string", "description": "Generated execution ID for this workflow run", "example": "123e4567-e89b-12d3-a456-426614174000" }, "channel": { "type": "string", "description": "Channel that will be used for message delivery (RCS, SMS, or MMS)", "example": "RCS" } }, "required": [ "phoneNumber", "executionId" ] } } ``` # Execute the Current Workflow Version POST https://api.smobi.com/v1/workflows/family/{workflowFamilyId}/execute/{phoneNumber} Authentication: x-api-key header (brand-scoped API key). Execute the current version of a workflow family for a single phone number ## Operation ```json { "description": "Execute the current version of a workflow family for a single phone number", "operationId": "executeFamily", "parameters": [ { "name": "workflowFamilyId", "required": true, "in": "path", "description": "ID of the workflow family to execute (will execute the current version)", "schema": { "example": "wf_123e4567-e89b-12d3-a456-426614174000", "type": "string" } }, { "name": "phoneNumber", "required": true, "in": "path", "description": "Phone number of the customer to execute the workflow for", "schema": { "example": "15552223333", "type": "string" } } ], "requestBody": { "required": true, "description": "Family workflow execution request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteFamilyWorkflow" }, "examples": { "withoutVariables": { "summary": "Without variables", "description": "Execute current version of workflow family", "value": {} }, "withVariables": { "summary": "With variables", "description": "Execute current version of workflow family with variables", "value": { "variables": { "customerName": "John Doe", "orderId": "12345", "discountCode": "SAVE20" } } } } } } }, "responses": { "201": { "description": "Family workflow execution result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteFamilyWorkflowResponse" }, "example": { "executionId": "run-123", "success": true, "channel": "RCS" } } } }, "400": { "description": "Bad Request - Invalid request parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid phone number format" }, "error": { "type": "string", "example": "Bad Request" }, "statusCode": { "type": "number", "example": 400 } } } } } }, "401": { "description": "Unauthorized - Invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" }, "statusCode": { "type": "number", "example": 401 } } } } } }, "404": { "description": "Not Found - Workflow family not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Workflow family not found" }, "error": { "type": "string", "example": "Not Found" }, "statusCode": { "type": "number", "example": 404 } } } } } }, "500": { "description": "Internal Server Error - No current version set for workflow family", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "No current version set for workflow family" }, "error": { "type": "string", "example": "Internal Server Error" }, "statusCode": { "type": "number", "example": 500 } } } } } } }, "summary": "Execute the Current Workflow Version", "tags": [ "Workflows" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "ExecuteFamilyWorkflow": { "type": "object", "properties": { "variables": { "type": "object", "description": "Map of workflow variable names to string values. Keys must match the selected workflow. Omit when no variables are needed.", "example": { "customerName": "John Doe", "orderId": "12345", "discountCode": "SAVE20" }, "additionalProperties": { "type": "string" } }, "settings": { "description": "Workflow execution settings including channel overrides", "example": { "channelOverride": "RCS" }, "allOf": [ { "$ref": "#/components/schemas/WorkflowExecutionSettings" } ] } } }, "WorkflowExecutionSettings": { "type": "object", "properties": { "channelOverride": { "type": "string", "description": "Channel override for the workflow execution", "enum": [ "RCS", "SMS" ], "example": "RCS" } } }, "ExecuteFamilyWorkflowResponse": { "type": "object", "properties": { "executionId": { "type": "string", "description": "Generated workflow run ID", "example": "run-123" }, "success": { "type": "boolean", "description": "Whether the execution was successful", "example": true }, "channel": { "type": "string", "description": "Channel that will be used for message delivery (RCS, SMS, or MMS)", "example": "RCS" }, "error": { "type": "string", "description": "Error message if execution failed", "example": "Invalid phone number" }, "errorCode": { "type": "string", "description": "Error code if execution failed", "enum": [ "INVALID_ARGUMENT", "RESOURCE_EXHAUSTED", "WORKFLOW_ALREADY_STARTED", "PAYLOAD_CONVERSION", "UNKNOWN", "WORKFLOW_NOT_FOUND", "SAVE_FAILED", "PREPARATION_FAILED", "USER_ENGAGED" ] }, "details": { "type": "object", "description": "Error-specific diagnostic object. Keys and value types vary by failure; no fixed set of properties is guaranteed.", "additionalProperties": true, "example": { "workflowStatus": "DRAFT", "requiredStatus": "ACTIVE" } } }, "required": [ "success" ] } } ``` # Execute a Workflow POST https://api.smobi.com/v1/workflows/{workflowDefinitionId}/execute/{phoneNumber} Authentication: x-api-key header (brand-scoped API key). Execute a workflow for a single phone number ## Operation ```json { "description": "Execute a workflow for a single phone number", "operationId": "executeSingle", "parameters": [ { "name": "workflowDefinitionId", "required": true, "in": "path", "description": "ID of the workflow definition to execute", "schema": { "example": "wf_123e4567-e89b-12d3-a456-426614174000", "type": "string" } }, { "name": "phoneNumber", "required": true, "in": "path", "description": "Phone number of the customer to execute the workflow for", "schema": { "example": "15552223333", "type": "string" } } ], "requestBody": { "required": true, "description": "Single workflow execution request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteSingleWorkflow" }, "examples": { "withoutVariables": { "summary": "Without variables", "description": "Execute workflow", "value": {} }, "withVariables": { "summary": "With variables", "description": "Execute workflow with variables", "value": { "variables": { "customerName": "John Doe", "orderId": "12345", "discountCode": "SAVE20" } } } } } } }, "responses": { "201": { "description": "Single workflow execution result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteSingleWorkflowResponse" }, "example": { "executionId": "run-123", "success": true, "channel": "RCS" } } } }, "400": { "description": "Bad Request - Invalid request parameters", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid phone number format" }, "error": { "type": "string", "example": "Bad Request" }, "statusCode": { "type": "number", "example": 400 } } } } } }, "401": { "description": "Unauthorized - Invalid API key", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Invalid API key" }, "error": { "type": "string", "example": "Unauthorized" }, "statusCode": { "type": "number", "example": 401 } } } } } }, "404": { "description": "Not Found - Workflow not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string", "example": "Workflow not found" }, "error": { "type": "string", "example": "Not Found" }, "statusCode": { "type": "number", "example": 404 } } } } } } }, "summary": "Execute a Workflow", "tags": [ "Workflows" ], "security": [ { "x-api-key": [] } ] } ``` ## Referenced Schemas ```json { "ExecuteSingleWorkflow": { "type": "object", "properties": { "variables": { "type": "object", "description": "Map of workflow variable names to string values. Keys must match the selected workflow. Omit when no variables are needed.", "example": { "customerName": "John Doe", "orderId": "12345", "discountCode": "SAVE20" }, "additionalProperties": { "type": "string" } }, "settings": { "description": "Workflow execution settings including channel overrides", "example": { "channelOverride": "RCS" }, "allOf": [ { "$ref": "#/components/schemas/WorkflowExecutionSettings" } ] } } }, "WorkflowExecutionSettings": { "type": "object", "properties": { "channelOverride": { "type": "string", "description": "Channel override for the workflow execution", "enum": [ "RCS", "SMS" ], "example": "RCS" } } }, "ExecuteSingleWorkflowResponse": { "type": "object", "properties": { "executionId": { "type": "string", "description": "Generated workflow run ID", "example": "run-123" }, "success": { "type": "boolean", "description": "Whether the execution was successful", "example": true }, "channel": { "type": "string", "description": "Channel that will be used for message delivery (RCS, SMS, or MMS)", "example": "RCS" }, "error": { "type": "string", "description": "Error message if execution failed", "example": "Invalid phone number" }, "errorCode": { "type": "string", "description": "Error code if execution failed", "enum": [ "INVALID_ARGUMENT", "RESOURCE_EXHAUSTED", "WORKFLOW_ALREADY_STARTED", "PAYLOAD_CONVERSION", "UNKNOWN", "WORKFLOW_NOT_FOUND", "SAVE_FAILED", "PREPARATION_FAILED", "USER_ENGAGED" ] }, "details": { "type": "object", "description": "Error-specific diagnostic object. Keys and value types vary by failure; no fixed set of properties is guaranteed.", "additionalProperties": true, "example": { "workflowStatus": "DRAFT", "requiredStatus": "ACTIVE" } } }, "required": [ "success" ] } } ``` # Authentication (/docs/authentication) ## Obtain an API Key [#obtain-an-api-key] Open the [Smobi dashboard](https://app.smobi.com), select the brand you are integrating, and open its API information. Copy the API key into your backend's secret storage. ## Authenticate Every Request [#authenticate-every-request] Send the key in the `x-api-key` header. The API base URL is `https://api.smobi.com/v1`. ```bash curl 'https://api.smobi.com/v1/contacts?limit=10' \ --header "x-api-key: $SMOBI_API_KEY" ``` Your key determines the brand scope. Do not add brand or organization IDs to contact API requests. Contacts endpoints require a brand-scoped key; an admin key is rejected. ## Keep Credentials Server-Side [#keep-credentials-server-side] Use environment variables or a secrets manager. Do not put keys in browser bundles, URLs, screenshots, analytics, or source control. If a key is exposed, replace it through your team's credential-management process and update the integration. ## Understand Authentication Errors [#understand-authentication-errors] | Status | Meaning | What to Check | | --- | --- | --- | | `401` | Missing or invalid key | Header name, key value, and selected environment | | `403` | Scope or permission rejected | Brand-scoped key and access to the requested resource | Documentation and downloadable schemas do not require a Smobi API key. Preview deployments may require a Smobi Vercel login. Run API examples from your backend or terminal with your brand’s API key; requests use the production API. ## Related Resources [#related-resources] - [Send Your First Message](/docs/quickstart) - [API Reference](/docs/api) # Inbound Message Payload Inbound V2 events contain customer replies and interactions. Inspect `channel` and the message subtype before reading the SMS or RCS content. Smobi sends these events to your configured HTTPS receiver. [Configure Webhooks](/docs/guides/webhooks) before processing events. ## Example Payloads ### SMS Text Message ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "to": "15552223333", "from": "15551234567", "channel": "SMS", "timestamp": "2021-01-01T00:00:00.000Z", "sms": { "content": "Hello, world!" } } ``` ### SMS with Media ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "to": "15552223333", "from": "15551234567", "channel": "SMS", "timestamp": "2021-01-01T00:00:00.000Z", "sms": { "content": "", "downloadUrl": "https://example.com/image.jpg", "fileExtension": "jpg" } } ``` ### RCS Text Message ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "to": "15552223333", "from": "15551234567", "channel": "RCS", "timestamp": "2021-01-01T00:00:00.000Z", "rcs": { "type": "TEXT", "content": "Hello from RCS!" } } ``` ### RCS Suggestion Interaction ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "to": "15552223333", "from": "15551234567", "channel": "RCS", "timestamp": "2021-01-01T00:00:00.000Z", "rcs": { "type": "SUGGESTION", "content": { "parentSmobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "parentClientRef": "client-ref-123", "suggestionId": "suggestion-123", "text": "Yes, I agree" } } } ``` ## Receiver Requirements - Validate the authentication headers configured for your receiver. - Persist or enqueue each accepted event before returning a successful `2xx` response. - Handle repeated events and out-of-order status updates. ## Payload Contract The following contract and referenced schemas describe the JSON body sent to your receiver. These are outbound events, not endpoints on `api.smobi.com`. [Download Webhook Schemas](/webhook-payloads.json). ```json { "$ref": "#/components/schemas/BaseOutboundMessageV2" } ``` ## Referenced Schemas ```json { "BaseOutboundMessageV2": { "type": "object", "properties": { "smobiMessageId": { "type": "string", "description": "The reference number of the message in the Smobi system", "example": "123e4567-e89b-12d3-a456-426614174000" }, "to": { "type": "string", "description": "The phone number of the recipient", "example": "15552223333" }, "from": { "type": "string", "description": "The phone number of the sender", "example": "15551234567" }, "channel": { "type": "string", "description": "The channel of the message - indicates which payload field to access", "enum": [ "SMS", "RCS" ], "example": "SMS" }, "timestamp": { "type": "string", "description": "The timestamp of the message", "example": "2021-01-01T00:00:00.000Z" }, "sms": { "description": "SMS message content (present when channel is SMS)", "allOf": [ { "$ref": "#/components/schemas/SmobiOutboundSmsMessageContentV2" } ] }, "rcs": { "description": "RCS message content (present when channel is RCS)", "oneOf": [ { "$ref": "#/components/schemas/SmobiTextMessageContentV2", "title": "Text" }, { "$ref": "#/components/schemas/SmobiContentMessageContentV2", "title": "Content" }, { "$ref": "#/components/schemas/SmobiLocationMessageContentV2", "title": "Location" }, { "$ref": "#/components/schemas/SmobiSuggestionMessageContentV2", "title": "Suggestion" } ] } }, "required": [ "smobiMessageId", "to", "from", "channel", "timestamp" ] }, "SmobiOutboundSmsMessageContentV2": { "type": "object", "properties": { "content": { "type": "string", "description": "Text content of the message", "example": "Hello, world!" }, "downloadUrl": { "type": "string", "description": "Download URL of media content", "example": "https://api-us.nexmo.com/v3/media/ae1467c0-1485-4fe5-b627-2a82e6eb856b" }, "fileExtension": { "type": "string", "description": "File extension of the media content", "example": "PNG" } } }, "SmobiTextMessageContentV2": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "TEXT" ], "description": "Type of message content", "example": "TEXT" }, "content": { "type": "string", "description": "Text content", "example": "Hello, world!" } }, "required": [ "type", "content" ] }, "SmobiContentMessageContentV2": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "CONTENT" ], "description": "Type of message content", "example": "CONTENT" }, "content": { "description": "Content details", "allOf": [ { "$ref": "#/components/schemas/SmobiContentDetailsV2" } ] } }, "required": [ "type", "content" ] }, "SmobiContentDetailsV2": { "type": "object", "properties": { "downloadUrl": { "type": "string", "description": "Download URL of the content", "example": "https://api-us.nexmo.com/v3/media/ae1467c0-1485-4fe5-b627-2a82e6eb856b" }, "fileExtension": { "type": "string", "description": "File extension of the content, always present when downloadUrl is present", "example": "PNG" } }, "required": [ "downloadUrl", "fileExtension" ] }, "SmobiLocationMessageContentV2": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "LOCATION" ], "description": "Type of message content", "example": "LOCATION" }, "content": { "description": "Location details", "allOf": [ { "$ref": "#/components/schemas/SmobiLocationDetailsV2" } ] } }, "required": [ "type", "content" ] }, "SmobiLocationDetailsV2": { "type": "object", "properties": { "latitude": { "type": "number", "description": "Latitude of the location", "example": 37.7749 }, "longitude": { "type": "number", "description": "Longitude of the location", "example": -122.4194 } }, "required": [ "latitude", "longitude" ] }, "SmobiSuggestionMessageContentV2": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "SUGGESTION" ], "description": "Type of message content - represents user interactions (reply/button)", "example": "SUGGESTION" }, "content": { "description": "Suggestion interaction details", "allOf": [ { "$ref": "#/components/schemas/SmobiSuggestionDetailsV2" } ] } }, "required": [ "type", "content" ] }, "SmobiSuggestionDetailsV2": { "type": "object", "properties": { "parentSmobiMessageId": { "type": "string", "description": "The ID of the parent message in the Smobi system", "example": "123e4567-e89b-12d3-a456-426614174000" }, "parentClientRef": { "type": "string", "description": "The client reference of the parent message", "example": "client-ref-123" }, "suggestionId": { "type": "string", "description": "The ID of the selected suggestion", "example": "suggestion-123" }, "text": { "type": "string", "description": "The text of the selected suggestion", "example": "Yes, I agree" } }, "required": [ "parentSmobiMessageId", "parentClientRef", "suggestionId", "text" ] } } ``` # Status Update Payload Status V1 events report message processing and delivery outcomes. Use `smobiMessageId` and `clientRef` to associate an update with the original request. Smobi sends these events to your configured HTTPS receiver. [Configure Webhooks](/docs/guides/webhooks) before processing events. ## Example Payloads ### Message Delivered ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "clientRef": "client-ref-123", "to": "15552223333", "from": "15551234567", "status": "DELIVERED", "channel": "SMS", "timestamp": "2021-01-01T00:00:00.000Z" } ``` ### Message Failed ```json { "smobiMessageId": "123e4567-e89b-12d3-a456-426614174000", "clientRef": "client-ref-123", "to": "15552223333", "from": "15551234567", "status": "FAILED", "channel": "SMS", "timestamp": "2021-01-01T00:00:00.000Z", "failureDetails": { "reason": "UNDELIVERABLE", "description": "The message could not be delivered" } } ``` ## Receiver Requirements - Validate the authentication headers configured for your receiver. - Persist or enqueue each accepted event before returning a successful `2xx` response. - Handle repeated events and out-of-order status updates. ## Payload Contract The following contract and referenced schemas describe the JSON body sent to your receiver. These are outbound events, not endpoints on `api.smobi.com`. [Download Webhook Schemas](/webhook-payloads.json). ```json { "$ref": "#/components/schemas/OutboundStatusV1" } ``` ## Referenced Schemas ```json { "OutboundStatusV1": { "type": "object", "properties": { "smobiMessageId": { "type": "string", "description": "The reference number of the message in the Smobi system", "example": "123e4567-e89b-12d3-a456-426614174000" }, "clientRef": { "type": "string", "description": "The reference number of the message in the client system", "example": "client-ref-123" }, "to": { "type": "string", "description": "The phone number of the recipient", "example": "15552223333" }, "from": { "type": "string", "description": "The phone number of the sender", "example": "15551234567" }, "status": { "type": "string", "description": "The status of the message", "enum": [ "PROCESSING", "SENT", "FAILED", "DELIVERED", "READ" ], "example": "DELIVERED" }, "channel": { "type": "string", "description": "The channel of the message", "enum": [ "SMS", "RCS" ], "example": "SMS" }, "timestamp": { "type": "string", "description": "The timestamp of the status update", "example": "2021-01-01T00:00:00.000Z" }, "failureDetails": { "description": "Additional details about the status (for failed messages)", "allOf": [ { "$ref": "#/components/schemas/WebhookStatusReasonV1" } ] } }, "required": [ "smobiMessageId", "clientRef", "to", "from", "status", "channel", "timestamp" ] }, "WebhookStatusReasonV1": { "type": "object", "properties": { "reason": { "type": "string", "description": "The reason for the status", "enum": [ "REJECTED", "OPTED_OUT", "UNDELIVERABLE" ], "example": "REJECTED" }, "description": { "type": "string", "description": "The description of the status reason", "example": "Resource size is too large" } }, "required": [ "reason" ] } } ``` # Audiences and Campaigns (/docs/guides/campaigns) ## Prepare Your Contacts [#prepare-your-contacts] Select the correct brand in the dashboard. Import or [sync contacts](/docs/guides/contacts), attach the attributes you need for targeting, and record consent accurately. Campaign eligibility requires an active, opted-in contact; importing a list does not grant consent. ## Build a Segment [#build-a-segment] Open [Segments](https://app.smobi.com/segments) and create a segment for your audience. The rule builder supports standard contact fields, tags, and custom fields. Combine conditions with AND when all must match, or OR when any may match. For example, use a tag to group customers in a program and a custom field to record their plan. Inspect the resulting membership before using the segment in a campaign. Data changes can change which contacts meet your rules. ## Configure the Campaign [#configure-the-campaign] Open [Campaigns](https://app.smobi.com/campaigns) and create a campaign: 1. Give it a name that identifies its purpose. 2. Choose the target segments and any segments to exclude. The audience combines included segments and removes excluded contacts. 3. Review the recipient estimate and preview. A segment's member count is not a guarantee of final send count because eligibility checks still apply. 4. Select the workflow to run. Choose a version strategy: follow the current version or select a specific available version. 5. Test the selected workflow using a phone you control and verify its content and variables. 6. Choose **Send Now** or **Schedule for Later**. For a scheduled campaign, check the date and time shown before confirming. Sending or scheduling is a real customer-facing action. Review the brand, audience, workflow version, and content before the final confirmation. ## Monitor Outcomes [#monitor-outcomes] Review the campaign's status and messaging results after launch. An accepted campaign or workflow is not proof of delivery to every recipient. Use [delivery webhooks](/docs/guides/webhooks) when your own system needs message-level outcomes. The public API reference covers contacts, individual messages, and workflow execution. Campaign and segment authoring in this guide use the dashboard; campaign and segment authoring endpoints are not part of this public API reference. ## Related Resources [#related-resources] - [Trigger Workflows](/docs/guides/workflows) - [Troubleshooting](/docs/troubleshooting) # Contacts and Consent (/docs/guides/contacts) ## Before You Begin [#before-you-begin] Obtain a [brand-scoped API key](/docs/authentication) and set `SMOBI_API_KEY` in your server environment. Use a phone number you control when testing. ## Create or Update a Contact [#create-or-update-a-contact] Contacts are upserted by phone number within your brand. Repeating the same upsert does not create another contact for that number. ```bash curl --request POST 'https://api.smobi.com/v1/contacts' \ --header "x-api-key: $SMOBI_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "phoneNumber": "15552223333", "firstName": "Alex", "tags": ["customer"], "customFields": { "plan": "standard" } }' ``` A new contact returns `201`; an existing contact update returns `200`. Save its `id`, or use its phone number in subsequent contact endpoints. ## Record Consent Accurately [#record-consent-accurately] New contacts default to `UNKNOWN`. **Campaigns send only to `OPTED_IN` contacts.** Set `marketingConsent` to `OPTED_IN` only after collecting consent; this value records your confirmation that the contact agreed to receive marketing messages. `OPTED_OUT` is always accepted. Trying to opt in a previously opted-out contact through the API returns `422`. The contact must opt back in through a supported channel, such as texting START. Do not work around suppression by recreating the contact. ## Update Attributes [#update-attributes] `PATCH /v1/contacts/{identifier}` applies only supplied fields: - Omit a field to leave it unchanged. - Send `null` to clear a nullable scalar field. - `tags` replaces the entire tag list. - `customFields` merges keys; a `null` value deletes that key. - `phoneNumber` is immutable. Tags and custom fields can be used in segment rules. Store stable external IDs in `customFields` when linking Smobi contacts to your application. ## Bulk Sync [#bulk-sync] `POST /v1/contacts/bulk` accepts a `contacts` array of up to **100** records. Results are reported per item, so inspect each result even when the HTTP request succeeds. Duplicate phone numbers within one batch reject the entire request with `400`. ## Read Every Page [#read-every-page] `GET /v1/contacts` returns cursor pagination. Pass `pagination.nextCursor` as the next request's `cursor` until `hasMore` is false. Supported filters include `phoneNumber`, `email`, repeatable `tag`, and `status` (`active`, `archived`, or `all`). URL-encode cursor and filter values. ## Archive or Restore [#archive-or-restore] `DELETE /v1/contacts/{identifier}` archives a contact and returns `204`. Archived contacts are hidden from default lists and excluded from campaigns and segment syncs. Repeat archiving is safe. Restore with `PATCH {"active": true}`. Archiving preserves the contact record. Clearing nullable profile fields is a separate update and does not establish that all associated personal data has been deleted. ## Related Resources [#related-resources] - [Contacts API Reference](/docs/api/contacts/upsert-contact) - [Audiences and Campaigns](/docs/guides/campaigns) # RCS and SMS Messaging (/docs/guides/messaging) ## Before You Begin [#before-you-begin] Complete the [quickstart](/docs/quickstart) and confirm which channels are configured for your brand. The examples below use the same brand-scoped API key. ## Choose the Message Content [#choose-the-message-content] `POST /v1/message` takes `clientRef`, `recipientPhoneNumber`, and `content`. Supply SMS content, RCS content, or both as appropriate for your brand's configuration. RCS supports text, media content, rich cards, carousels, and suggested actions. Include a useful SMS alternative when you want a message to work for recipients who cannot receive RCS. Final channel selection also depends on brand configuration and recipient capability. ```json { "clientRef": "order-123-ready", "recipientPhoneNumber": "15552223333", "content": { "rcs": { "content": { "type": "TEXT", "content": "Your order is ready to collect." } }, "sms": { "content": "Your order is ready to collect." } } } ``` See [Send a Message](/docs/api/messaging/send-message) for the full content schemas and examples. ## Check Channel Support [#check-channel-support] ```bash curl --request POST 'https://api.smobi.com/v1/check-channel-support/15552223333' \ --header "x-api-key: $SMOBI_API_KEY" ``` This check requires an RCS agent configured for your brand. It creates or updates the customer’s capability record and records API usage. The response reports `rcs` and `sms` capability. Capability is not marketing consent; check your own permission to contact the recipient. ## Correlate and Track Messages [#correlate-and-track-messages] Store `clientRef` and the returned `smobiMessageId`. Handle [status webhooks](/docs/events/status) to track delivery. Keep message acceptance separate from delivery in your application. `clientRef` is a correlation identifier. It is not documented as an idempotency key and should not be used to deduplicate sends. After an ambiguous timeout, reconcile the outcome before resending. ## Phone Numbers and Media [#phone-numbers-and-media] Include the country code. Digits-only numbers such as `15552223333` avoid URL encoding ambiguity; E.164 numbers include a leading `+`. URL-encode a leading `+` when placing it in a query or path. For SMS media, `mediaUrl` supplies the media and `content` is the accompanying text. Use a reachable HTTPS asset URL and test the resulting message on the target channel before a campaign. ## Related Resources [#related-resources] - [Send a Message](/docs/api/messaging/send-message) - [Receive Webhooks](/docs/guides/webhooks) # TypeScript SDK (/docs/guides/typescript-sdk) ## Before You Begin [#before-you-begin] Obtain a [brand-scoped API key](/docs/authentication), configure `SMOBI_API_KEY` in your server environment, and confirm that your brand has a messaging sender. Use a phone number you control for these examples. ## Install [#install] ```bash npm install smobi-sdk@0.3.11 ``` This guide is checked against version `0.3.11`. Its generated types require `content.rcs`, so the example supplies RCS text with an SMS fallback. For SMS-only sends, use the [HTTP quickstart](/docs/quickstart). ## Create a Client and Send a Message [#create-a-client-and-send-a-message] ```typescript import { SmobiClient } from 'smobi-sdk'; const apiKey = process.env.SMOBI_API_KEY; if (!apiKey) throw new Error('SMOBI_API_KEY is required'); const client = new SmobiClient({ apiKey }); const result = await client.messaging.sendMessage( { clientRef: 'quickstart-001', recipientPhoneNumber: '15552223333', content: { rcs: { content: { type: 'TEXT', content: 'Hello from Smobi!' } }, sms: { content: 'Hello from Smobi!' }, }, }, { maxRetries: 0 }, ); console.log(result.smobiMessageId); ``` Run this on your backend and replace the recipient with your own phone number. The example disables automatic retries for a send so an ambiguous failure is not blindly resubmitted. ## Run a Workflow [#run-a-workflow] ```typescript const execution = await client.workflows.executeSingle( 'YOUR_WORKFLOW_DEFINITION_ID', '15552223333', { variables: { customerName: 'Alex' } }, { maxRetries: 0 }, ); ``` ## Supported Resources and Request Options [#supported-resources-and-request-options] The SDK exposes `messaging` and `workflows` resources. For contact management, use the [Contacts HTTP API](/docs/guides/contacts). Request options include `timeoutInSeconds`, `maxRetries`, and `abortSignal`. Keep a clear retry policy for message sends and workflow executions to avoid duplicate messages or executions after an uncertain response. Consult the [API Reference](/docs/api) for request and response schemas. Review API compatibility and test your integration before upgrading the SDK. ## Related Resources [#related-resources] - [API Reference](/docs/api) - [Troubleshooting](/docs/troubleshooting) # Receive Webhooks (/docs/guides/webhooks) ## Choose the Events You Need [#choose-the-events-you-need] | Event | Purpose | Payload | | --- | --- | --- | | Status update | Track processing, sending, delivery, reads, and failures | [Status V1](/docs/events/status) | | Inbound message | Receive SMS or RCS replies and interactions | [Inbound V2](/docs/events/inbound) | These are requests **from Smobi to your server**. The receiver examples are not endpoints you should call on `api.smobi.com`. ## Configure Your Receiver [#configure-your-receiver] 1. Create an HTTPS endpoint in your application that accepts JSON POST requests. 2. In the dashboard's webhook configuration for your brand, enter the inbound and/or status webhook URL. 3. Select the payload version your receiver supports. This reference describes status V1 and inbound V2. 4. Configure an authentication header for the receiver, such as `Authorization: Bearer YOUR_WEBHOOK_SECRET`. Use a separate secret from your Smobi API key. 5. Send a test message to a phone you control and confirm the corresponding events reach your receiver. Test phone numbers can have their own webhook URLs and headers. If events appear at an unexpected receiver, check the test-number overrides as well as the brand settings. ## Authenticate Incoming Requests [#authenticate-incoming-requests] Validate the custom authentication header before processing the body. Smobi sends the authentication headers configured for your receiver. Use those headers to authenticate the request; HMAC signature verification is not part of the documented webhook contract. ## Process Reliably [#process-reliably] Accept the event, persist or enqueue the work, then return a successful `2xx` response promptly. Make processing idempotent and tolerate repeated or out-of-order status updates. For status events, keep `smobiMessageId` and `clientRef` to associate the event with your original request. Automatic retry timing is not guaranteed by the public contract. Monitor receiver failures and delivery logs, and work with your Smobi representative to recover missed events when needed. For inbound V2, use `channel` to choose `sms` or `rcs`. RCS replies may be text, media, location, or suggestion interactions; inspect the event subtype rather than assuming every reply is a text string. ## Related Resources [#related-resources] - [Status Update Payload](/docs/events/status) - [Inbound Message Payload](/docs/events/inbound) # Trigger Workflows (/docs/guides/workflows) ## Prepare a Workflow [#prepare-a-workflow] Create and configure your workflow in the Smobi dashboard, then obtain its definition ID from the workflow's integration information. Define the variables it expects and test the flow with a phone you control before using customer data. The public API executes existing workflows. It does not expose workflow authoring endpoints. ## Execute for One Recipient [#execute-for-one-recipient] ```bash curl --request POST \ 'https://api.smobi.com/v1/workflows/YOUR_WORKFLOW_DEFINITION_ID/execute/15552223333' \ --header "x-api-key: $SMOBI_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "variables": { "customerName": "Alex", "orderId": "12345" } }' ``` Use variable names that match your workflow, with string values. Send `{}` when it needs no variables. Save the execution response for tracking; an accepted execution does not mean every step has completed. ## Follow the Current Workflow Version [#follow-the-current-workflow-version] Use `POST /v1/workflows/family/{workflowFamilyId}/execute/{phoneNumber}` when your integration should execute the family's current version. Use the definition endpoint when you want to target a specific definition. The family must have a current version configured. ## Execute a Batch [#execute-a-batch] `POST /v1/workflows/{workflowDefinitionId}/execute` accepts an `executions` array: ```json { "executions": [ { "phoneNumber": "15552223333", "variables": { "customerName": "Alex" } }, { "phoneNumber": "15554445555", "variables": { "customerName": "Sam" } } ] } ``` Each batch must contain **1–25 executions**. Repeating the same phone-number string in one batch rejects the request. Batch routing is selected by the server; supplied `settings` values do not override it. Use the single-recipient endpoint with `settings.channelOverride` when you need a supported channel override. Split larger inputs into smaller batches. A `202` response indicates acceptance for processing. Do not assume a retry is deduplicated: avoid resubmitting a batch whose outcome is uncertain. See the [workflow API reference](/docs/api/workflows/execute-single) for request schemas, settings, and error responses. ## Related Resources [#related-resources] - [Receive Webhooks](/docs/guides/webhooks) - [TypeScript SDK](/docs/guides/typescript-sdk) # Smobi Documentation (/docs) Smobi brings RCS and SMS messaging, contacts, campaigns, and automated workflows into one platform. ## Start Here [#start-here] ## Choose Your Starting Point [#choose-your-starting-point] Use the [dashboard](https://app.smobi.com) to manage your brand, audiences, campaigns, and workflows. Use the API to sync contacts, send messages, and trigger workflows from your backend. For an existing integration, review [Authentication](/docs/authentication), [Receive Webhooks](/docs/guides/webhooks), and [Troubleshooting](/docs/troubleshooting). ## Resources for AI Coding Agents [#resources-for-ai-coding-agents] Use [llms.txt](/llms.txt) to discover documentation pages or [llms-full.txt](/llms-full.txt) to retrieve the complete text. The [OpenAPI document](/openapi.json) describes callable endpoints. Each page also has a **Copy Markdown** action. # Send Your First Message (/docs/quickstart) ## Before You Begin [#before-you-begin] Sign in to the [Smobi dashboard](https://app.smobi.com), select your brand, and obtain its API key. Your brand needs a configured messaging sender. If your brand is not ready to send, complete setup with your Smobi contact first. Use a phone you control for the first test. API examples call the production API and can send real messages. ## 1. Store Your API Key [#1-store-your-api-key] Keep the key in a server-side environment variable. Never place it in frontend code or commit it to Git. ```bash export SMOBI_API_KEY='YOUR_BRAND_API_KEY' ``` ## 2. Send a Message [#2-send-a-message] Replace the recipient with your own phone number, including its country code. Use a unique `clientRef` to correlate this message with your application. ```bash curl --request POST 'https://api.smobi.com/v1/message' \ --header "x-api-key: $SMOBI_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "clientRef": "quickstart-001", "recipientPhoneNumber": "15552223333", "content": { "sms": { "content": "Hello from Smobi!" } } }' ``` ## 3. Save the Message Identifier [#3-save-the-message-identifier] A successful request returns HTTP `201` with `smobiMessageId` and `status`. Store the identifier alongside your `clientRef`. An accepted request is not proof that the phone received the message: sending and delivery happen asynchronously. Configure a [status webhook](/docs/guides/webhooks) to track `SENT`, `DELIVERED`, `READ` where supported, or `FAILED`. ## Next Steps [#next-steps] - [Add RCS content and an SMS fallback](/docs/guides/messaging). - [Sync contacts and record marketing consent](/docs/guides/contacts). - [Use the TypeScript SDK](/docs/guides/typescript-sdk). If a request fails, use the [troubleshooting guide](/docs/troubleshooting). Before retrying a send after a timeout, check whether the original request was accepted to avoid sending a duplicate message. # Troubleshooting (/docs/troubleshooting) ## Start with the Response [#start-with-the-response] Capture the HTTP status, error body, request time, and relevant message or execution identifier. Redact API keys, webhook secrets, and customer data from shared logs. | Symptom | Check | | --- | --- | | `400` | Required fields, country code, JSON types, batch size, and duplicate contacts in a bulk request | | `401` | The `x-api-key` header and the key value | | `403` | Brand scope and permission to access the resource | | `404` | Resource ID, selected brand, or workflow family/definition | | `422` | Consent transition or workflow validation errors described in the response | | `429` | Slow the request rate; honor `Retry-After` if supplied | | Timeout or `5xx` | The operation may have been accepted; reconcile before retrying sends or workflows | ## Message Accepted but Not Received [#message-accepted-but-not-received] Acceptance and delivery are separate. Check status webhooks, the selected recipient number, brand sender readiness, recipient capability, and suppression. Where a failure event contains `failureDetails`, preserve it with the message record. ## Contact Does Not Receive a Campaign [#contact-does-not-receive-a-campaign] Confirm that the contact is active, has `marketingConsent: OPTED_IN`, and belongs to the campaign's selected audience. Importing or creating a contact alone does not grant consent. An archived or opted-out contact is not made eligible by adding tags. ## Webhook Does Not Reach the Expected URL [#webhook-does-not-reach-the-expected-url] Check the brand's URL, payload version, and authentication headers. Also inspect test-phone overrides. Confirm your receiver is publicly reachable over HTTPS and accepts the configured authentication. Review response status and logs on both sides. ## Batch Rejected [#batch-rejected] Contact bulk upserts allow 100 records; workflow batches allow 25 executions. These are separate limits. Split inputs accordingly and inspect per-item contact results. ## Get Help [#get-help] Contact your Smobi representative with the endpoint, time, sanitized error, and message or execution ID. Never send your API key or webhook secret.