How Bluelink likes to read and write data for our awesome partners!
This document covers two primary concepts: Data formats and preferred API specs for reading and writing data.
These specs are strong preferences, but not hard requirements. Building to these specs will
Bluelink uses flexible and verbose data models. All fields are nullable and may be omitted if no data exists, unless explicitly stated otherwise. The structure may seem complicated for simple use cases, however it supports more complicated uses such as systems supporting multiple emails for a single individual.
By default, Bluelink writes data to partners in these formats. For partners with existing APIs (e.g., NGPVAN), we can easily and correctly convert between their format and ours. Therefore, we strongly encourage new partners to use these formats when building new APIs.
This is an example of the data format as a JSON object with example values.
{
"given_name":"Jane",
"family_name":"Voter",
"source":"Salesforce",
"emails":[
{
"address":"jane@gmail.com"
}
],
"identifiers":[
{
"source":"VAN:MyVoters",
"identifier":"101234567"
},
{
"source":"SALESFORCE",
"identifier":"0038A00000Z7XYZABC"
},
{
"source":"COOLBLUE",
"identifier":"321superblueid"
}
],
"addresses":[
{
"address_lines":[
"123 4th St",
"Apt 5"
],
"city":"Seattle",
"state":"WA",
"postal_code":98103,
"country":"US"
}
],
"details":{
"favorite_color":"Blue",
"notes":[
{
"date":"2020-01-19",
"note":"Ask how the remodel is going."
}
]
},
"created_date": 1643673600000,
"modified_date": 1643992027000
}
This is the top-level person object. There are nested fields that may be repeated.
Field Name | Description | Type |
---|---|---|
given_name | First name / given name | String |
family_name | Last name / family name | String |
salutation | Preferred greeting / envelope name | String |
emails | Email addresses | Array of Email Objects |
identifiers | External identifiers, e.g., VAN:MyVoters ID | Array of Identifier Objects |
phones | Phone numbers | Array of Phone Objects |
addresses | Postal addresses | Array of Address Objects |
tags | Simple tags that apply to the person, e.g., DONOR | Array of Tag Objects |
flags | Similar to tags, but with more configuration options | Array of Flag Objects |
employer | Name of the person’s employer | String |
employer_address | Postal address of the person’s employer | Address Object |
occupation | Occupation | String |
source | Original source of this record. E.g., “Salesforce” | String |
birthdate | ISO 8601 formatted birth date: YYYY-MM-DD | String |
geographies | Geographical data, e.g., a congressional district | Array of Geography Objects |
scores | Numeric scores, e.g., partisanship model | Array of Score Objects |
details | Additional data | Details Object |
conversation | Canvassing conversation | Conversation Object |
actions | Array of actions associated with this person, such as canvassing conversations, donations, and events attended. The format will likely be specific to the type of data you collect. Contact us to discuss the data types and formats that are relevant to you. | Custom object. Example: Conversation Object |
created_date | Timestamp when created in millis since epoch | Integer |
modified_date | Timestamp of last update in millis since epoch | Integer |
Field Name | Description | Type |
---|---|---|
id | Email ID, if applicable | String |
id_source | System this ID is from. If omitted, assumed to be Bluelink | String |
primary | True if this is known to be the primary email | Boolean |
address | Address, e.g., “user@gmail.com” | String |
type | Type, e.g., “personal”, “work” | String |
status | One of “Potential”, “Subscribed”, “Unsubscribed”, “Bouncing”, or “Spam Complaints” | String |
Field Name | Description | Type |
---|---|---|
id | Geo Object ID, if applicable | String |
id_source | System this ID is from. If omitted, assumed to be Bluelink | String |
layer_type | Type of geography, e.g., congressional district | String |
name | The name of this geographical district, e.g., “CD 3” | String |
state | US state in ISO-3166-2 format | String |
source | Original source of this geographical data | String |
Field Name | Description | Type |
---|---|---|
source | External system to which this ID belongs, e.g., “VAN:myCampaign”. See notes below | String |
identifier | Case-sensitive ID in the external system | String |
details | Additional data | Details Object |
Notes:
Bluelink has standardized strings for source. Using these will allow us to correctly understand the external IDs you add. source
(unlike identifier
) is case insensitive. We just like all caps.
Field Name | Description | Type |
---|---|---|
id | Phone ID, if applicable | String |
id_source | System this ID is from, e.g., “VAN:myCampaign” | String |
primary | True if this is known to be the primary phone | Boolean |
number | Number; may or may not include country code | String |
description | Free-form description | String |
type | Type, e.g., “Home”, “Work”, “Mobile” | String |
country | ISO 3166-1 Alpha-2 country code | String |
sms_capable | True if this number can accept SMS | Boolean |
do_not_call | True if this number is on the US FCC Do Not Call Registry | Boolean |
details | Additional data | Details Object |
Field Name | Description | Type |
---|---|---|
id | Address ID, if applicable | String |
id_source | System this ID is from | String |
primary | True if this is known to be the primary address | Boolean |
type | Address type. One of “Home” (address where registered to vote), “Work”, “Mailing”, or “Other” | String |
venue | Venue name, if relevant | String |
address_lines | Street address including optional second line, e.g., unit number | Array of Strings |
city | City or other locality | String |
state | State in ISO 3166-2 format or other regional subdivision | String |
postal_code | Zip or other postal code | String |
country | ISO 3166-1 Alpha-2 country code | String |
status | A value representing the status of the address. One of “Potential”, “Verified”, or “Bad” | String |
Field Name | Description | Type |
---|---|---|
score | Numeric score; meaning varies. Up to ~1e9 with 2 decimal places | Float |
score_type | Type, e.g., “Partisanship model” | String |
source | Original source of this score | String |
Note: “source” and “score_type” should be unique per person, effectively, an ID.
Field Name | Description | Type |
---|---|---|
tag | Tag string; convention is either a simple string or a string with a prefix separated by a colon, e.g., “DONOR:GRASSROOTS” | String |
Field Name | Description | Type |
---|---|---|
tag | Tag string; convention is either a simple string or a string with a prefix separated by a colon, e.g., “DONOR:GRASSROOTS” | String |
action | What action to take with this flag in destination systems. One of “ADD”, “REMOVE”. Use flags instead of tags if you need the ability to remove tags in external systems. | String |
A JSON object with whatever fields are necessary. Standardization is specific to the needs of the pipeline. Or just a nice place to put other data.
Conversations, including canvass results and survey questions (Note: this model is a work in progress)
Field Name | Description | Type |
---|---|---|
timestamp | Time the contact occured | Integer of milliseconds since epoch, or timestamp with timezone in ISO format (E.g. 2020-12-10T23:45:59+01:00 ) |
contact_type | How the person was contacted. E.g. Phone, Door Knock. | String |
contacted_phone (optional) | Phone number that was contacted; may or may not include country code | String |
result | Result of the contact. E.g. Canvassed, Not Home, Moved, Refused. | String |
survey_responses | List of survey responses from this conversation | Array of Survey Response Objects |
Note: You can use the Bluelink mapping API or UI to provide a mapping between your system’s contact types/result types and the matching types in third parties you want to sync to.
Field Name | Description | Type |
---|---|---|
question_id | ID of the question in your system. E.g. 12345 or “support”. | String or Integer |
question_name (optional) | Name of the question in your system. E.g. “Candidate Support” | String |
question_text (optional) | Full text of the question in your system. E.g. “Do you support our candidate?” | String |
response_id | ID of the response in your system. E.g. 12345 or “true”. | String or Integer |
response_text (optional) | Full text of the response in your system. E.g. “Yes” or “Strong Support”. | String |
external_question_ids (optional) | IDs of matching questions in any third party systems. Alternatively, mappings between your system and others can be provided in the Bluelink mapping API or UI. | Array of Identifier Objects |
external_response_ids (optional) | IDs of matching responses in any third party systems. Alternatively, mappings between your system and others can be provided in the Bluelink mapping API or UI. | Array of Identifier Objects |
Bluelink works most reliably when we can query the partner’s API. This is our preferred API to call which we can easily support in a reliable way.
The partner API should use basic HTTP auth with a secret API key or token as the password. The username will be used to identify the Bluelink client.
Example:
curl -u "bluelink-123:secret-api-key" \
"https://api.partner-service.org/v1/profile"
In this example the username is “bluelink-123”. “123” is the Bluelink Client ID.
The password, “secret-api-key” is the partner API key that was entered into Bluelink UI. Bluelink securely stores these keys using multiple layers of encryption.
Host and path prefix: https://api.<your_url>/v1
Host and path prefix for dev: https://api-dev.<your_url>/v1
A versioned path will make it simpler to change in the future, but is not required. We can work with whatever host and path you use.
If you have a development, staging or QA instance, we’d love to build against that! It is not required, but extremely helpful.
path: /profile
verb: GET
To validate the auth is correct, we’d like to hit an endpoint and get client metadata back. It should respond with status code 200 and the client name (or similar) if the credentials are valid.
Example:
curl -u "bluelink-123:secret-api-key" \
"https://api.partner-service.org/v1/profile"
Returns:
headers: “Content-type: application/json”
status code: 200
payload:
{"client_name": "Awesome Progressive Org"}
Invalid credentials should return the proper HTTP status code, e.g., 401 or 403.
path: /people
verb: POST
headers: “Content-type: application/json”
payload: The JSON representation of the Person Object Data Model including nested fields
Bluelink will send person data to your service. Your service decides if it wants to add a new person, update an existing person, or reject the data outright. It would be entirely responsible for merging any fields to handle an update.
Important: To avoid an infinite update loop, the services should not set the updated timestamp on the record if no data has changed.
Example:
curl -X POST \
-u "bluelink-123:secret-api-key" \
-H "Content-type: application/json" \
-d '{"given_name": "Jane", "family_name": "Voter"}' "https://api.partner-service.org/v1/people"
On success, this can return either 204 with no content, or a 200 with whatever content you care to provide. Often, returning the newly created or newly updated data can be useful if Bluelink will do more with the data after updating your system.
path: /people
verb: GET
query params:
updated_min
: The inclusive minimum “updated date” timestamp in ms. Records updated before this timestamp are not included. If omitted, there is no min updated date filter, effectively time 0.updated_max
: The exclusive maximum “updated date” timestamp in ms. Records updated on or after this timestamp are not included. If omitted, there is no max updated date filter, effectively “now”.page_id
: something to indicate which page to fetch if paginating the response.
next_page_id
. Bluelink will use this in the followup request.page_size
param as long as you have a default that works for you.fields
: a comma separated list of fields to return for people matching the other filters.
emails
or phones
.This will return a list of all people updated within the range provided in the query params. It need only provide the fields listed in the “fields” query param, but may include more.
Example:
curl -u "bluelink-123:secret-api-key" \
"https://api.partner-service.org/v1/people/?created_min=1608080540000&created_max=1608083540000&fields=emails,phones&page_id=0"
Returns:
headers: “Content-type: application/json”
status code: 200
payload:
{
"people":[
{
"given_name":"Jane",
"family_name": "Voter",
"source": "CoolBlue",
"emails": [
{
"address":"jane@gmail.com"
},
{
"address":"jane@bluelink.org"
}
],
"phones": [
{
"number":"123-456-7890"
}
]
},
{
"given_name":"Annie",
"family_name": "Ado",
"source": "CoolBlue",
"emails": [],
"phones": []
}
],
"next_page_id": "1234"
}
This is the simplest way to get data into Bluelink’s systems. You can post person and/or activity data to our API endpoint, and it will be saved in our system for use in your pipelines.
The API endpoint is https://api.bluelink.org/webhooks/
.
Use basic HTTP auth to authenticate to our API. You can get a username and password at https://app.bluelink.org/bluelink-webhook-integration. If you are building an integration used by multiple clients, request one username/password per client.
Example:
curl -X POST \
-u "bluelink-123:secret-key" \
-H "Content-type: application/json" \
-d '{...webhook data...}'
"https://api.bluelink.org/webhooks/"
Each webhook request should represent a creation or update to one person’s data, and/or creation or update of one activity.
The webhook request should contain the following fields:
Field Name | Description | Type |
---|---|---|
source (required) | String to identify that the data came from your system. For example, your company name. We assume that all IDs (such as activity ID and topic ID) are IDs from this source system. | String |
person (required) | The person to create or updated or the person associated with the activity. If the person is being created or updated, include a full person object with any fields you want added/changed. If you’re creating an activity for an existing person, we will use “person” to identify which person the activity is for. In that case you could include just an “identifier” for the person. A single request can create/update both a person and an activity. | Person Object |
activity (optional) | The activity to create or update. | Work with us to determine the best format to send your app’s activity data to Bluelink. |
All subfields of person and activity are optional – send the data you have.
Here is a sample webhook requests to submit a person to Bluelink.
Create/update a person
{
"source": "COOLBLUE",
"person":{
"given_name":"Jane",
"family_name":"Voter",
"source":"Salesforce",
"emails":[
{
"address":"jane@gmail.com"
}
],
"identifiers":[
{
"source":"VAN:MyVoters",
"identifier":"98765432"
},
{
"source":"SALESFORCE",
"identifier":"0038A00000Z7XYZABC"
},
{
"source":"COOLBLUE",
"identifier":"321superblueid"
}
],
"addresses":[
{
"address_lines":[
"123 4th St",
"Apt 5"
],
"city":"Seattle",
"state":"WA",
"postal_code":98103,
"country":"US"
}
],
"details":{
"favorite_color":"Blue",
"notes":[
{
"date":"2020-01-19",
"note":"Ask how the remodel is going."
}
]
}
}
}
To run a pipeline, we’ll need some info for the specific client.
© 2022 Bluelink Data Systems, Inc
4301 50th Street NW, Suite 300, PMB 1011, Washington, DC 20016