--- title: "On API Call Starter" slug: "transactional-journeys-on-api-call-starter" description: "The On API Call starter lets your backend trigger real-time transactional messages, like OTPs or order confirmations, directly through an API request." updated: 2026-09-02T12:06:39Z published: 2026-09-02T12:07:41Z canonical: "academy.insiderone.com/transactional-journeys-on-api-call-starter" --- > ## Documentation Index > Fetch the complete documentation index at: https://academy.insiderone.com/llms.txt > Use this file to discover all available pages before exploring further. # On API Call Starter > **Suggested readings:** [Transactional Journeys](/v1/docs/transactional-journeys), [Use On API Call Starter](/v1/docs/transactional-journeys-use-on-api-call-starter) The On API Call starter, also known as triggering real-time transactional messages such as OTPs or order confirmations directly via API, is the entry point for transactional journeys triggered directly via an API request. It allows backend systems to explicitly enroll users into a transactional journey and send messages in real time. This starter is designed for use cases where message delivery must happen immediately after a system action, without relying on behavioral data or segmentation inside Architect. Each API request is treated as an explicit instruction to start a journey instance. Common examples include order confirmations, password resets, OTP delivery, shipment notifications, and appointment reminders. This guide explains the following concepts: - [API structure](/v1/docs/transactional-journeys-on-api-call-starter#api-structure) - [Rate limits and quotas](/v1/docs/transactional-journeys-on-api-call-starter#rate-limits-and-quotas) - [Dynamic attributes](/v1/docs/transactional-journeys-on-api-call-starter#dynamic-attributes) - [Response types](/v1/docs/transactional-journeys-on-api-call-starter#response-types) ## API structure ### Trigger endpoint Transactional journeys using On API Call are triggered via the following endpoint: ```Endpoint POST https://architect.useinsider.com/v1/transactional/send ``` This endpoint accepts requests that target a specific transactional journey and enqueue users into that journey. > [!WARNING] > The journey must be in the **Active** status to accept requests. Requests targeting **inactive** or **non-existing** journeys are **rejected**. > [!NOTE] > Only **one** journey can be targeted per request. **Up to 100** users can be targeted per request. ### Request lifecycle When a request is sent to the trigger endpoint, Transactional Journeys processes it in the following order: 1. **Authentication:** The request is authenticated using partner credentials. 2. **Payload validation**: The request body is validated against the required schema. 3. **Rate limiting:** Request-level and user-level quotas are enforced. 4. **Idempotency check:** Duplicate requests are rejected using the idempotency key. 5. **Journey resolution:** The target journey is validated and checked for Active status. 6. **User queuing:** Valid users are queued for immediate execution. Once accepted, journey execution starts asynchronously and is processed within the 30-second end-to-end SLA (service level agreement). ### Authentication Requirements Each request must include the following headers. Requests missing these headers or using invalid credentials are rejected. | X-PARTNER-NAME | | --- | | X-INS-AUTH-KEY | | Content-Type: application/json | You can create an [API authentication token](https://academy.insiderone.com/docs/api-authentication-tokens) for Transactional Journeys via **Settings** > **Integration Settings** > **API Keys**. Root-level schema: ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": number        }        ],        "users": [        {        "identifier": {        "type": "email | phone | iid | uuid",        "value": "string"        },        "dynamic_attributes": { }        }        ]        } ``` ### Idempotency behavior Each request must include a unique idempotency_key. Idempotency is evaluated **per request**, not per user. It **prevents** **duplicate** processing, rejects **repeated** **submissions** with the same key, and **prevents** **faulty** integration. If a duplicate key is detected, the request is rejected, and no messages are resent. Below is an example idempotency key usage: **Order confirmation**: order-{order_id}-confirmation **Shipping update**: order-{order_id}-shipped **Appointment reminder**: appointment-{appointment_id}-reminder ### User identification Each request must include at least one user. Supported identifier types are **email**, **phone number** (E.164 format), **IID** (Insider ID), and **UUID** (unique user ID). > [!NOTE] > Each user is evaluated **independently**. A single request can enter **up to 100** users into the journey, meaning that if some users fail validation while others pass, the request is **partially** **accepted**. ### Create users By default, the trigger looks up users in UCD read-only, so a recipient who is not yet in UCD is dropped with user_not_found and **cannot** **receive** a transactional message. To reach recipients who are not yet in your customer database (e.g., first-time guest buyers, brand-new sign-ups) in the same call, add the optional create_user boolean to the user object. - The value is false by default. When **omitted** or **false**, behavior is unchanged. UCD is queried read-only, and an unknown recipient is dropped with user_not_found. - When it is true and the recipient does **not exist**, the user is created in UCD from the single identifier and enters the journey in the same request. - When it is true and the recipient already exists, they are matched and entered without duplication. - The created profile holds **only** the sent identifier. dynamic_attributes, to_email, and to_phone_number are never written to the UCD profile. - Users can be created from **email**, **phone**, or **UUID** only. **iid** (Insider ID) can still be used to match an existing user, but a new user **cannot be created** from it, since the Insider ID is generated by UCD when the profile is created. The identifier type must be configured as an identifier in your Identity Resolution Management (IRM) settings; otherwise, the user fails with identifier_not_registered. - A created profile only holds the identifier it was created from, but you can still reach the user on another channel in the same journey by passing a delivery override. For example, create the user from an email identifier and include to_phone_number on the user object so that a later SMS element is delivered to that number. Refer to [Recipient Override](https://academy.insiderone.com/docs/transactional-journeys-on-api-call-starter#:~:text=Recipient%20override%20(to_email%20and%20to_phone_number)). Below is an example request that creates a user. ```json {  "idempotency_key": "order-confirm-8841",  "targets": [ { "type": "journey_id", "value": 1001 } ],  "users": [    {      "identifier": { "type": "email", "value": "new-alice@example.com" },      "create_user": true,      "dynamic_attributes": { "order_no": "8841" }    }  ] } ``` ## Rate limits and quotas Rate limits are enforced at both the request and the user level. The On API Call can process **250 requests per minute** and **25,000 users per minute**. Rate limit headers are returned in every response, including remaining quota and retry timing. If a limit is exceeded, the request is rejected, and no users enter the journey. Below are rate limit header examples: ```Ratelimits X-RateLimit-Limit: 250        X-RateLimit-Remaining: 245 ``` ## Dynamic attributes Dynamic attributes can be sent with the request to personalize message content. Dynamic attributes: - Are available in **Email**, **SMS,** and **WhatsApp** **inner pages**. - Do **not** **use** any value from **UCD**. - **Apply** only to the **current** **journey** entry. To add **Attachments** for Email, you need to send them in the API Request in the following format. ```json {    "idempotency_key": "string",    "targets": [        {            "type": "journey_id",            "value": 156        }    ],    "users": [        {            "identifier": {                "type": "email",                "value": "string"            },            "dynamic_attributes": {                "attachments": [                    {                        "name": "INV-12345.pdf",                        "url": "https://example.com/invoices/INV-12345.pdf",                        "content_type": "application/pdf"                    },                    {                        "name": "Warranty.pdf",                        "url": "https://example.com/docs/warranty.pdf",                        "content_type": "application/pdf"                    }                ],                "order": {                    "items": [                        {                            "name": "Blue Shirt",                            "price": "345"                        }                    ]                }            }        }    ] } ``` All three fields are mandatory to be sent for the **Attachments** type dynamic content: - **name**: Appears as the Attachment's file name in the email - **url**: Required for downloading the attachment - **type**: Required for validation > **Up to 5** attachments can be sent in a single API request per user. ### CC and BCC email addresses You can pass CC and BCC recipients via the API request. Use the keys cc_email_addresses and bcc_email_addresses as top-level fields within each user object, alongside identifier and dynamic_attributes, to specify additional recipients for each user. ```json {  "idempotency_key": "b7d5d464-4051-4e13-b126-036af2da386b",  "targets": [    {      "type": "journey_id",      "value": 27    }  ],  "users": [    {      "identifier": {        "type": "email",        "value": "sameer@weatherpromise.com"      },      "cc_email_addresses": [        "emre.boyacioglu@insiderone.com"      ],      "bcc_email_addresses": [        "emir.tiris@insiderone.com"      ],      "dynamic_attributes": {        "first_name": "Sameer"      }    }  ] } ``` > [!WARNING] > You can add **up to 5** email addresses per field. The **cc** and **bcc** keys are reserved and **cannot** be used as general dynamic attribute names. > **CC** and **BCC** values apply per user entry. Each user in the request can have their own distinct **CC** and **BCC** recipients. **Recipient override (****to_email** **and** **to_phone_number****)** By default, transactional messages are delivered to the email address or phone number stored in the user's CDP profile. In some cases, you may want to deliver a message to a different address for a specific journey trigger. For example, when a single user makes multiple bookings or orders, each with a different communication address per booking. You can override the delivery destination for a single API call by passing to_email and/or to_phone_number as top-level fields inside each user object, alongside identifier, cc_email_addresses, bcc_email_addresses, and dynamic_attributes. ```json {  "idempotency_key": "booking-77001",  "targets": [    {      "type": "journey_id",      "value": 27    }  ],  "users": [    {      "identifier": {        "type": "email",        "value": "alice@example.com"      },      "to_email": "booking-77001@partner.test",      "to_phone_number": "+15551234567",      "dynamic_attributes": {        "booking_id": "77001"      }    }  ] } ``` **Behavior**: - to_email must be in **RFC 5322** format. to_phone_number must be in **E.164 format** (e.g., +15551234567). Both fields are optional, and empty or omitted values fall back to the CDP profile address. - identifier is still required. It drives user resolution, journey eligibility, idempotency, and quotas. - The user's CDP profile is not modified. Only the delivery destination changes for this request. - If the identifier is not found in CDP, the user still appears in failed_users as user_not_found. The override does not bypass user resolution. - Each override only applies to its matching channel. to_email is used by Email elements, to_phone_number by SMS and WhatsApp elements. The unused field is ignored. ### Nested objects Nested objects are supported up to 3 levels deep. Nested attributes are flattened automatically and can be referenced using dot notation. **Level 1 object→ user name** ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": 1001        }        ],        "users": [        {        "identifier": {        "type": "email",        "value": "string"        },        "dynamic_attributes": {        "user": {        "name": "Sarah"        }        }        }        ]        } ``` **Level 2 object→** **order.shipping.address** ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": number        }        ],        "users": [        {        "identifier": {        "type": "email | phone | iid | uuid",        "value": "string"        },        "dynamic_attributes":        { "order": {        "shipping": {        "address": "123 Main St"        }        }        }        }        ]        } ``` **Level 3 object→** **order.items.first.name** ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": number        }        ],        "users": [        {        "identifier": {        "type": "email | phone | iid | uuid",        "value": "string"        },        "dynamic_attributes":        {        "order": {        "items": {        "first": {        "name": "Blue Shirt"        }        }        }        }        }        ]        } ``` ### Arrays with nested objects Level 2 objects and one array index are used → order.items.0.title ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": 1001        }        ],        "users": [        {        "identifier": {        "type": "email",        "value": "string"        },        "dynamic_attributes": {        "order": {        "items": [        {        "title": "Blue Shirt",        "sku": "SKU-001"        }        ]        }        }        }        ]        } ``` > [!NOTE] > Array indices (index = .0.) don't count toward the 3-level limit. Below is an **invalid** example. **Level 4 object ->** **user.profile.contact.email** ```json {        "idempotency_key": "string",        "targets": [        {        "type": "journey_id",        "value": 1001        }        ],        "users": [        {        "identifier": {        "type": "email",        "value": "string"        },        "dynamic_attributes": {        "user": {        "profile": {        "contact": {        "email": "user@example.com"        }        }        }        }        }        ]        } ``` > [!NOTE] > Keep nesting to 2-3 levels for simplicity. Use arrays for collections of items. ## Response types ### Success responses #### HTTP 202 Accepted ```json {        "success": true,        "status": "accepted",        "users_queued": 1,        "failed_users": []        } ``` #### HTTP 202 Accepted **Partial success response**: only valid users will enter the journey. ```json {        "success": true,        "status": "accepted",        "users_queued": 3,        "failed_users": [        {        "identifier": {        "type": "email",        "value": "invalid-email"        },        "error": "schema_validation_failed"        }        ]        } ``` When a request uses create_user, the success response includes a users_created count: the number of queued users newly provisioned in UCD. It is **0** when the flag is **not used** or when every flagged recipient **already** **existed**. ```json {  "success": true,  "status": "accepted",  "users_queued": 1,  "users_created": 1,  "failed_users": [] } ``` Two additional per-user error values can appear in failed_users when create_user is used: - identifier_not_registered: the identifier type is not configured as an identifier in the partner's Identity Resolution Management (IRM) settings. - user_creation_failed: creation was attempted but did not complete. The identifier type cannot create a user (e.g., iid), or UCD rejected the creation. ### Failed responses #### HTTP 400 Bad Request ```json {        "error": "schema_validation_failed",        "code": 1002,        "message": "invalid request body",        "details": {        "invalid_fields": {        "users[0].identifier.type": ["must be one of iid phone email uuid"]        }        }        } ``` #### Malformed to_email - HTTP 400 Bad Request ```json {  "error": "schema_validation_failed",  "code": 1002,  "message": "invalid request body",  "details": {    "invalid_fields": {      "users[0].to_email": ["must be a valid email address"]    }  } } ``` #### Malformed to_phone_number - HTTP 400 Bad Request ```json {  "error": "schema_validation_failed",  "code": 1002,  "message": "invalid request body",  "details": {    "invalid_fields": {      "users[0].to_phone_number": ["must be a valid E.164 phone number"]    }  } } ``` #### Idempotency Conflict - HTTP 400 Bad Request ```json {        "error": "idempotency_conflict",        "code": 3003,        "message": "idempotency_conflict"        } ``` #### Rate Limit Error - HTTP 429 Too Many Requests ```json {        "error": "too_many_requests",        "code": 2001,        "message": "too many requests"        } ``` #### Authentication Error - HTTP 401 Unauthorized ```json {        "error": "unauthorized",        "code": 1203,        "message": "invalid API key"        } ``` #### Server Error - HTTP 500 Internal Server Error ```json {        "error": "internal_server_error",        "code": 3000,        "message": "unexpected server error"        } ```