Custom activities guide

Activities describe user behavior like clicking on an email, making a purchase or logging in to your application. They help you understand what your customers are doing, segment customers into audiences and create detailed reports. Custom activities allow you to design your own activities that represent behaviors across the customer lifecycle.

Activities are used to enter people into playbooks and create target audiences, reports and dashboards.

This guide shows you how to add your own custom activities to Autopilot via the API. Custom activities will help you find deep insights into your business via reporting and filtering, and have fine-grained control about how and when you communicate to your audiences.

At the end of this guide you will be sending custom activities from your own system into Autopilot.

Getting started

The example activity for this guide is Logged in. We create an activity each time someone logs into our app.

In addition, we would like to know:

  • Which payment plan they are on.

  • The location they logged in.

  • If they are the account owner.

  • Which web browser they are using.

This information needs to be associated with the customer who logged in. We would also like to add a new person to Autopilot if one does not exist to match this activity.

Generating a private API key

Learn more about this process in Configuring a custom API key.

Do I need more than one data source?

Each Custom API data source has its own API Key associated with it. It is fine to use the same API key for sending many kinds of activities from within your application. However, if they are different systems, and you may wish to disconnect or regenerate the key separately, perhaps if one system was compromised in some way, then you can create more than one.

Each data source has its own statistics, logs, activities location graph and activities list associated with it, so if you would like to separate data on that basis you can create a separate Custom API data source for each.

See also

Creating a custom activity

The next step is to go to Activities and create a login activity. To do this navigate to the Activities screen and click New activity.

Activity name

This should be a verb such as Purchased, Visited, Downloaded or Logged in - it will be used in the activity feed for items such as:

Art Vandalay logged in.

You will be able to edit the activity name later, but it will not update previously recorded activities in the activity feed for a person. It will update it in the filters, reports and other places where it is displayed dynamically.

Icon

The icon is associated with your custom activity and will appear in the person activity feed and filters. While it isn’t necessary to have a unique icon per activity, it helps you identify them quickly.

Attributes

Any attributes you want to send along with your activities must be defined here. If they are not, they will be ignored by the API.

When you name attributes you don’t have to give them programmatic names. These names will appear in filters and likely be used by non-technical people in your company. So you can call them things like Product name, or Size preference.

Once you create the activity the system will give them internal field names based on their name and type, e.g. str:cm:size-preference, which you will use when sending requests to the API.

You are required to define the type of each field, these include:

Type Description Code example

Text

Any combination of letters and numbers below 255 characters.

{"str:cm:browser-type":"Microsoft Windows"}

Long text

Text longer than 255 characters

{"txt:cm:story":"It was a dark and stormy night. In her attic bedroom Margaret Murry, wrapped in an old patchwork quilt, sat on the foot of her bed and watched the trees tossing in the frenzied lashing of the wind. Behind the trees clouds scudded frantically across the sky. Every few moments the moon ripped through them, creating wraithlike shadows that raced along the ground."}

Number

A whole number.

{"int:cm:age":40}

Decimal number

A decimal number.

{"int:cm:weight":64280}

Currency

A decimal number displayed as a currency.

{"int:cm:price":24990}

Date

A specific day, month and year.

{"dtz:cm:renewal-date":{"year": 2022,"month": 3,"day": 4,"timezone":"Australia/Sydney"}}

Time and date

A specific time, day, month and year.

{"tme:cm:event-date":"2021-01-20T00:07:00.698Z"}

Boolean

True or false.

{"bol:cm:is-owner":true}

Phone number

A local or international phone number.

{"phn:cm:phone":{"c":"61","n":"403464042"}}

Link

A web page or URL.

{"str:cm:mylink":"https://autopilotapp.com"}

Why are decimals and currencies sent as "int"?

In order to not lose precision in decimal operations internally, the Autopilot API treats decimals as integers, multiplied by 1000.

For a decimal number, multiply the number by 1000. So for 64.28, you send 64280. For currency, multiply the number by 1000 and no $. So for $24.99, you send 24990.

See also

Do not add attribute for person fields like First name, Last name and Email

Person fields can be sent with the payload and not need to add attributes for these. You can pass these into the create activity call along with the person to either automatically create people to go with the activity, or update existing people with the new data.

Map to CRM

The map to CRM allows you to use attributes sent in your payload to update the fields on the person in question for that activity. For example, you might want to create custom fields on people for each of the attributes you are sending, then update them each time you send an activity payload. You can also update fields like First name and Last name in case the activity caused an internal update to this information. Mapping to fields is optional.

Conversion value

If you enable conversion value, you can provide an extra value in your payload like so "int::v": 1530. This value is then considered to have been attributed to this activity. Since the value is a currency, you multiply the number you are sending by 1000. In the above example the value being sent represents $15.30. So we would do this if we thought every login to our web app was worth $15.30.

Track as touch

This option will update the "Last seen" on the person associated with the activity. A login event is the perfect example of when you would want to track the activity as touch because we know we have seen the user at the time they logged in.

Passive activities which were not initiated by the user should leave this option disabled.

Create an activity in Autopilot

For our login demo we will use the Private API key generated in previous steps.

Below is a basic example of sending a login activity to Autopilot via the API using the cURL command. This sends through the attributes we specified above when creating the activity.

curl --location\
     --request POST 'https://api.ap3api.com/v1/activities/create'\
     --header 'X-Api-Key: PUT-YOUR-REAL-KEY-HERE'\
     --header 'Content-Type: application/json'\
     --data-raw '{
       "activities": [
         {
           "activity_id":"act:cm:logged-in",
           "fields": {
             "str::first": "Chris",
             "str::last": "Smith",
             "str::email": "chris.smith@example.com"
           },
           "attributes": {
             "str:cm:payment-plan": "$100 starter plan",
             "bol:cm:account-owner": true,
             "str:cm:web-browser": "Google Chrome",
             "int::v": 1530
           },
           "location": {
             "source_ip": "59.167.135.194"
           }
         }
       ]}'

Create activity response

If the request is successful you will receive a 200 OK response, and a JSON body like the following:

{
  "activities": [
    {
      "person_id": "00600e153df0a3b67ce60f00",
      "person_status": "created",
      "activity_id": "00600e153c548806f6a4dbda",
      "status": "ingested"
    }
  ]
}

person_id is the ID of the person which the activity was added to. This could be a new person_id if the person was never seen before based on your merge strategy. You can also provide person_id in subsequent requests instead of email.

person_status tells you what happened to the person associated with this activity:

person_status Description

invalid

A field needed to identify or create the person - as specified in your data source merge criteria - was not provided. Or you provided a person field which does not exist or does exist but the wrong kind of data was provided.

created

A person was created for this activity as it did not previously exist.

merged

A person already existed based on your data source merge criteria, and its fields were updated with any provided.

skipped

A person already existed and your data source merge strategy asked for these to be skipped, so the person was not updated based on the fields provided. The activity, however, was recorded against this person.

by-id

You specified a person_id in the request and the person was found and matched. The activity was applied to this person.

Autopilot is strongly typed which means if you provide a type of data which cannot be unmarshaled into the correct type, your activity ingestion will fail.

Additionally, if you provide person fields which do not exist, activity ingestion will fail. Note that this doesn’t apply for unknown attributes on activities, which will simply be ignored.

Providing person fields as part of the payload

When you set up your data source, you specify your merge preferences. This is how the system decides what to do when it encounters a person it has seen before.

In order for an activity to create a new person if the person hasn’t been seen before, or apply the activity to an existing person, you need to provide person fields in your payload.

Our previous code example included the "fields" section, and that is what we are referring to here:

{
  "fields": {
    "str::first": "Chris",
    "str::last": "Smith",
    "str::email": "chris.smith@example.com"
  }
}

If you provide unknown person fields, the activity ingestion will fail.

Custom activity request limits

It is possible to send up to 100 activities in the one payload to Autopilot. The limits are as follows:

  • 100 activities max

  • 2 MB total payload size

See also

Here is an example sending two login activities at once:

curl --location\
     --request POST 'https://api.ap3api.com/v1/activities/create'\
     --header 'X-Api-Key: PUT-YOUR-REAL-KEY-HERE'\
     --header 'Content-Type: application/json'\
     --data-raw '{
       "activities": [
         {
           "activity_id":"act:cm:logged-in",
           "fields": {
             "str::first": "Alex",
             "str::last": "Smith",
             "str::email": "alex.smith@example.com"
         },
         "attributes": {
             "str:cm:payment-plan": "$1900 maxi plan",
             "bol:cm:account-owner": false,
             "str:cm:web-browser": "Edge"
            },
            "location": {
              "source_ip": "127.0.0.1"
            }
          },
          {
            "activity_id":"act:cm:logged-in",
            "fields": {
              "str::first": "Chris",
              "str::last": "Smith",
              "str::email": "chris.smith@example.com"
            },
            "attributes": {
              "str::email": "john@example.com",
              "str:cm:payment-plan": "$100 starter plan",
              "bol:cm:account-owner": true,
              "str:cm:web-browser": "Google Chrome"
            },
            "location": {
              "custom": {
                "country_name": "USA",
                "region_name": "NY",
                "city_name": "New York",
                "position_name": "Vandalay Industries",
                "lat": 40.730610,
                "lng": -73.935242
              }
            }
          }
        ]
      }'

A successful response will contain multiple results to let you know how each activity in the payload went:

{
  "activities": [
    {
      "person_id": "00600e15bbf0a3b67ce61200",
      "status": "ingested",
      "person_status": "created",
      "activity_id": "00600e15bb548806f6a4dfde"
    },
    {
      "person_id": "00600e15bbf0a3b67ce61500",
      "status": "ingested",
      "person_status": "created",
      "activity_id": "00600e15bb548806f6a4e1e0"
    }
  ]
}

Asynchronous activity ingestion

If you do not want to wait for a response from the API in order to ingest activities, you can provide the async: true flag in your request. You might want to do this if your code runs synchronously and the requests to Autopilot delay your response to the user.

When the request contains the async: true flag, the system will queue ingestion of the activities and respond to you immediately.

curl --location\
     --request POST 'https://api.ap3api.com/v1/activities/create'\
     --header 'X-Api-Key: PUT-YOUR-REAL-KEY-HERE'\
     --header 'Content-Type: application/json'\
     --data-raw '{
       "async": true,
       "activities": [
         {
           "activity_id":"act:cm:logged-in",
           "fields": {
             "str::first": "Chris",
             "str::last": "Smith",
             "str::email": "chris.smith@example.com"
           },
           "attributes": {
             "str:cm:payment-plan": "$100 starter plan",
             "bol:cm:account-owner": true,
             "str:cm:web-browser": "Google Chrome"
           },
           "location": {
             "source_ip": "59.167.135.194"
           }
         }
       ]
     }'

If the request is successful you will get a response like the following:

{
  "activities":
    [
        {
            "status":"queued",
            "person_status":"queued"
        }
    ]
}

Note the "status": "queued" in the response.

Geo locating activities

You can optionally provide a location with each activity you send Autopilot. In fact, if you do not know the location we can even look it up for you based on IP address. Read about geo locating activities here

Back-dating activities

If you would like to back-date activity data, you can use the "created" field in your request to specify the date on which the activity occurred. Autopilot allows you to send in backdated events up to 90 days in the past.

curl --location\
     --request POST 'https://api.ap3api.com/v1/activities/create'\
     --header 'X-Api-Key: PUT-YOUR-REAL-KEY-HERE'\
     --header 'Content-Type: application/json'\
     --data-raw '{
       "activities": [
         {
           "activity_id":"act:cm:logged-in",
           "created": "2020-02-17T01:30:17.601Z",
           "fields": {
             "str::first": "Chris",
             "str::last": "Smith",
             "str::email": "chris.smith@example.com"
           },
           "attributes": {
             "str:cm:payment-plan": "Free plan",
             "bol:cm:account-owner": true,
             "str:cm:web-browser": "Netscape Navigator"
           },
           "location": {
             "source_ip": "59.167.135.194"
           }
         }
       ]
     }'

As this documentation will age, if you copy the above example verbatim you might get a response like below:

{
  "request_id": "19ccd5c1-112d-4dfa-bf80-0a3312896742",
  "code": 400,
  "error":" Created date is more than 90 days ago"
}

If you provide a "created" date within the last 90 days, you will receive a successful response:

{
    "activities":
        [
            {
                "person_id":"00602c446af92b7aa1f77a00",
                "status":"ingested",
                "person_status":"created",
                "activity_id":"00602c71a9b346c32c9b7473"
            }
        ]
}

To read more about Autopilot’s data retention policy, see the link below:

See also

  • Data retention policy

Email subscription permission

All people added via the API are considered to be "opted in" for email subscription permission. They will appear with "Subscribed via API" on their profile. It is very important that you do not add people via the API if you do not have permission to email them.

If you would like to control either their subscription status, such as setting them as "opted out", or setting their subscribed or unsubscribed reason, you can do this by providing the following fields:

{
  "bol::p": true,
  "str::s-ctx": "Subscribed via internal API"
}

Above is an example of email subscription permission being enabled with the message "Subscribed via internal API" Here is an example of someone who starts off unsubscribed:

{
  "bol::p": false,
  "str::u-ctx": "Unsubscribed via internal API"
}

The default, if you do not provide these fields is:

{
  "bol::p": true,
  "str::s-ctx": "Subscribed via API"
}

Here is a full cURL request example for how to do this:

curl --location\
     --request POST 'https://api.ap3api.com/v1/activities/create'\
     --header 'X-Api-Key: PUT-YOUR-REAL-KEY-HERE'\
     --header 'Content-Type: application/json'\
     --data-raw '{
       "activities":[
         {
           "activity_id":"act:cm:logged-in",
           "fields": {
             "str::first": "Chris",
             "str::last": "Smith",
             "str::email": "chris.smith@example.com",
             "bol::p": false,
             "str::u-ctx": "Unsubscribed via internal api"
           },
           "attributes": {
             "str:cm:payment-plan": "Free plan",
             "bol:cm:account-owner": true,
             "str:cm:web-browser": "Netscape Navigator"
           }
         }
       ]
      }'

Displaying custom activities

When you send custom activities into Autopilot, you can display them in custom formats within Autopilot using attributes from the activities.

Here is an example of an activity using custom code to display:

Order number {{attribute.number}} placed for item {{attribute.item_name}}

The syntax of this is slightly different to the Liquid syntax used in campaigns. All attributes from the custom activity are available and are put as lowercase and spaces are replaced with underscores.