On API Call Starter

Prev Next

Suggested readings: 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

Trigger endpoint

Transactional journeys using On API Call are triggered via the following 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.

The journey must be in the Active status to accept requests. Requests targeting inactive or non-existing journeys are rejected.

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 for Transactional Journeys via Settings > Integration Settings > API Keys.

Root-level schema:

{
        "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).

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.

Below is an example request that creates a user.

{
  "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:

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.

{
    "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.

{
  "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"
      }
    }
  ]
}

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.

{
  "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

{
        "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

{
        "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

{
        "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

{
        "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"
        }
        ]
        }
        }
        }
        ]
        }

Array indices (index = .0.) don't count toward the 3-level limit.

Below is an invalid example.

Level 4 object -> user.profile.contact.email

{
        "idempotency_key": "string",
        "targets": [
        {
        "type": "journey_id",
        "value": 1001
        }
        ],
        "users": [
        {
        "identifier": {
        "type": "email",
        "value": "string"
        },
        "dynamic_attributes": {
        "user": {
        "profile": {
        "contact": {
        "email": "user@example.com"
        }
        }
        }
        }
        }
        ]
        }

Keep nesting to 2-3 levels for simplicity. Use arrays for collections of items.

Response types

Success responses

HTTP 202 Accepted

{
        "success": true,
        "status": "accepted",
        "users_queued": 1,
        "failed_users": []
        }

HTTP 202 Accepted

Partial success response: only valid users will enter the journey.

{
        "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.

{
  "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

{
        "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

{
  "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

{
  "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

{
        "error": "idempotency_conflict",
        "code": 3003,
        "message": "idempotency_conflict"
        }

Rate Limit Error - HTTP 429 Too Many Requests

{
        "error": "too_many_requests",
        "code": 2001,
        "message": "too many requests"
        }

Authentication Error - HTTP 401 Unauthorized

{
        "error": "unauthorized",
        "code": 1203,
        "message": "invalid API key"
        }

Server Error - HTTP 500 Internal Server Error

{
        "error": "internal_server_error",
        "code": 3000,
        "message": "unexpected server error"
        }