GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing developer.drip.com (truncated) — Drip API Reference
REST API (aka Broadcasts)
Shopper Activity JS API Custom Dynamic Content Drip API DocumentationIntroductionWelcome to the Drip API! The REST API communicates exclusively in JSON over SSL (HTTPS).
All endpoint URLs begin with Parameters must be serialized in JSON and passed in the request body (not in the query string or form parameters). You should use the media type designation of REST SectionAuthentication
The REST API accepts both token-based authentication (intended for private integrations), and full OAuth 2 authentication (for public integrations). OAuthFor public integrations with Drip, you must use OAuth based authentication. Here's a quick overview of how to get going.
If you're not going to use a library and will be rolling your own OAuth 2 client, here's the long form of how you can get going.
Rate Limiting
You can make up to 50 requests per hour for batch endpoints and 3,600 individual requests per hour for other API endpoints. Each batch request accommodates up to 1,000 records in the payload for a total of 50,000 updates per hour. To minimize your API requests, use the Batch API. Batch APICreate or update a batch of subscribers
We recommend using this API endpoint when you need to create or update a collection of subscribers at once. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Unsubscribe a batch of subscribers
HTTP Endpoint
Arguments
Record a batch of events
We recommend using this API endpoint when you need to record a collection of events at once that will likely exceed the regular rate limit of 3,600 requests per hour. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Create or update a batch of carts
We recommend using this API endpoint when you need to import a collection of carts that will likely exceed the regular rate limit of 3,600 requests per hour. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Create or update a batch of orders (Legacy)
We recommend using this API endpoint when you need to create a collection of orders and subscribers at once that will likely exceed the regular rate limit of 3,600 requests per hour. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Create or update a batch of orders
We recommend using this API endpoint when you need to import a collection of orders that will likely exceed the regular rate limit of 3,600 requests per hour. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Create or update a batch of products
We recommend using this API endpoint when you need to import a collection of products that will likely exceed the regular rate limit of 3,600 requests per hour. Note: Since our batch APIs process requests in the background, there may be a delay between the time you submit your request and the time your data appears in the user interface. HTTP Endpoint
Arguments
Pagination
Some endpoints that return collections are paginated. For these endpoints,
the By default, the API will return the first page of results. Each page contains
a maximum of 100 items, unless otherwise noted below. To fetch a particular page,
use the Errors
Drip responds to invalid requests in a number of different ways. Exceptions to these rules will be noted for any applicable endpoints. Assume authentication is required on all endpoints unless otherwise noted. If you attempt to anonymously access a resource that requires authentication, you will receive a Validation Errors
If validation errors occur while attempting to create or update a resource, you will receive a
Transition Errors
State transition endpoints will respond with a Accounts
Properties
List all accounts
HTTP Endpoint
ArgumentsNone. Fetch an account
Sending addressesFetching a single account also returns the account's email addresses, newest first. Only those
with
HTTP Endpoint
ArgumentsNone. Single-Email Campaigns
|
| Status | Description |
|---|---|
draft |
Not yet scheduled. Drafts are the only Single-Email Campaigns that can be edited via the API. |
scheduled |
Scheduled for a future send. Read-only. |
sending |
Currently being sent. Read-only and cannot be deleted. |
sent |
Completed. Read-only. |
canceled |
Canceled before sending. Read-only. |
deleted |
Deleted. Hidden from default listings. |
send_at and localize_sending_time are read-only on this resource. Set them with
Schedule a Single-Email Campaign, and set who receives it
with Set the audience.
Single-Email Campaigns are represented as follows:
{
"id": "123456",
"status": "sent",
"name": "4 Marketing Automation Trends for 2015",
"from_name": "John Doe",
"from_email": "[email protected]",
"from_email_address_id": "1234567",
"reply_to": "[email protected]",
"reply_to_email_address_id": "1234569",
"postal_address": "123 Anywhere St\nFresno, CA 99999",
"localize_sending_time": true,
"send_at": "2015-07-01T10:00:00Z",
"bcc": null,
"created_at": "2015-06-21T10:31:58Z",
"href": "https://api.getdrip.com/v2/9999999/broadcast/123456",
"preview_url": "https://www.getdrip.com/broadcasts/123456/2d83a64861f23b1c35a3b8d6ee3b54f7",
"subject": "4 Marketing Automation Trends for 2015",
"html_body": "HTML body",
"text_body": "Text body",
"links": {
"account": "9999999"
}
}
All responses containing Single-Email Campaign data also include the following top-level link data:
{
"links": {
"broadcasts.account": "https://api.getdrip.com/v2/accounts/{broadcasts.account}",
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each Single-Email Campaign record. |
status |
Returns whether the Single-Email Campaign is draft, canceled, scheduled, sent or sending. |
name |
The private name given to the Single-Email Campaign. |
from_name |
A "from name" that appears in your sent emails and can be changed on a per email basis. This setting overrides the account's default from name. |
from_email |
The address the Single-Email Campaign is sent from: its own From address, or the account's default From address if it has none. Read-only; set it with from_email_address_id. |
from_email_address_id |
The id of the address in from_email, matching an id in the account's sending addresses. Returns null if neither the campaign nor the account has a From address, or if the address has been deleted; from_email still shows it. See Sender. |
reply_to |
The Reply-To address, or null when replies go to the From address. Read-only; set it with reply_to_email_address_id. |
reply_to_email_address_id |
The id of the address in reply_to, matching an id in the account's sending addresses, or null. It is also null if the Reply-To address has been deleted; reply_to still shows it. See Sender. |
postal_address |
As required by the CAN-SPAM Act, this is a postal address used for all sent emails and can be changed on a per email basis. |
localize_sending_time |
The scheduled send_at time if set to be sent in the subscriber's time zone. |
send_at |
The timestamp representing when the Single-Email Campaign will be delivered. |
bcc |
A list of emails designated to receive a blind copy of the Single-Email Campaign. Read-only. |
created_at |
A read-only Drip generated timestamp for when the Single-Email Campaign was first created. |
href |
The url designated for retrieving the account record via the REST API. |
preview_url |
A read-only public URL for previewing the email content. Does not require authentication. Returns null for deleted Single-Email Campaigns. |
subject |
The Single-Email Campaign's subject. |
html_body |
The HTML content used in the email's body. |
text_body |
The plain text content used in the email's body. |
derived |
Read-only text generated when the campaign is read. derived.audience describes who it goes to in plain English, such as People who are in the "VIP customers" segment, or All people. It is display text: do not parse it, and expect its wording to change. |
links |
An object containing the account's REST API URL. |
The content object structure:
{
"content": {
"html": {
"type": "document",
"value": "<html><body><h1>Hello!</h1></body></html>"
},
"text": {
"type": "document",
"value": "Hello!\n\nWelcome to our newsletter."
}
}
}
Email content is provided via a content object containing content type keys. Each key maps
to an object with a type (the content variant) and a value (the content itself).
| Key | Description |
|---|---|
html |
Required when creating. The HTML email content. Currently the only supported type is document: a full HTML document with a complete <html> structure. Supports Liquid templating. |
text |
Optional. The plain text version of the email. Currently the only supported type is document: a full plain text document. Supports Liquid templating. If omitted, the plain text version is generated automatically from the HTML. |
HTML content is validated and sanitized for security: JavaScript is not allowed, and dangerous CSS patterns are blocked.
The HTML must also include two Liquid tags, as the CAN-SPAM Act requires:
{{ unsubscribe_url }} or {{ unsubscribe_link }}{{ postal_address }}, {{ inline_postal_address }} or {{ html_postal_address }}Both usually go in the footer. The check is on the HTML itself, so setting the postal_address
field does not satisfy it on its own. Without them, creating or updating the campaign responds
with a 422 Unprocessable Entity:
{
"errors": [{
"code": "can_spam_compliance",
"attribute": "content.html",
"message": "Content HTML must include {{ postal_address }} or {{ inline_postal_address }} or {{ html_postal_address }}"
}]
}
To change who a draft Single-Email Campaign is from and where replies go:
curl -X PATCH "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"broadcasts": [{
"from_name": "Acme Support",
"from_email_address_id": "1234568",
"reply_to_email_address_id": "1234569"
}]
}
EOF
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID")
request = Net::HTTP::Patch.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/json"
request.body = {
broadcasts: [{
from_name: "Acme Support",
from_email_address_id: "1234568",
reply_to_email_address_id: "1234569"
}]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
broadcasts: [{
from_name: "Acme Support",
from_email_address_id: "1234568",
reply_to_email_address_id: "1234569"
}]
})
}
);
const body = await response.json();
If an address id cannot be used, responds with a
422 Unprocessable Entityand changes nothing:
{
"errors": [{
"code": "validation_error",
"attribute": "from_email_address_id",
"message": "[email protected] is not verified: use the id of an address in the account's sending_addresses that has verified: true."
}]
}
A Single-Email Campaign's From name, From address and Reply-To address can be set when you
create it, and changed while it is a draft with
Update a Single-Email Campaign.
from_name is free text, up to 255 characters. It cannot be blank, and supports Liquid
templating.id from the
account's sending addresses, returned by
Fetch an account.from_email_address_id must be an address with verified: true.reply_to_email_address_id must be an address with verified: true and
auto_generated: false.null or "" to clear either one. A cleared From address sends from the
account's default From address, and a cleared Reply-To sends replies to the From address.Any other id, such as an unverified or deleted address, or one that belongs to another account,
responds with a 422 Unprocessable Entity and changes nothing.
from_email, reply_to and bcc are read-only: they are returned in responses. Set the sender
with the address ids above.
A sender set here appears in the Drip app, the same as one chosen there.
To list Single-Email Campaigns in an account:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.broadcasts
if response.success?
puts response.body["broadcasts"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { status: "sent" };
client.listBroadcasts(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The broadcasts property is an array of broadcast objects.
{
"links": { ... },
"meta": {
"page": 1,
"sort": "created_at",
"direction": "asc",
"count": 5,
"total_pages": 1,
"total_count": 5,
"status": "all"
},
"broadcasts": [ ... ]
}
GET /v2/:account_id/broadcasts
| Key | Description |
|---|---|
page |
Optional. The page number (1-indexed). Defaults to 1. |
per_page |
Optional. The number of results per page, up to a maximum of 100. Defaults to 100. |
status |
Optional. Filter by one of the following statuses: draft, scheduled, sending, sent, canceled, deleted, or all. By default, deleted Single-Email Campaigns are excluded; use status=deleted or status=all to include them. |
sort |
Optional. Sort results by one of these fields: created_at, updated_at, send_at, or name. Defaults to created_at. |
direction |
Optional. Filter sort direction with: asc or desc. Defaults to asc. |
To create a Single-Email Campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"broadcasts": [{
"name": "December Newsletter",
"subject": "Your December Newsletter is here!",
"preheader": "See what's new this month...",
"content": {
"html": {
"type": "document",
"value": "<html><body><h1>Hello!</h1></body></html>"
},
"text": {
"type": "document",
"value": "Hello! Welcome to our newsletter."
}
},
"postal_address": "123 Main St, Minneapolis, MN 55401"
}]
}
EOF
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts")
request = Net::HTTP::Post.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/json"
request.body = {
broadcasts: [{
name: "December Newsletter",
subject: "Your December Newsletter is here!",
preheader: "See what's new this month...",
content: {
html: {
type: "document",
value: "<html><body><h1>Hello!</h1></body></html>"
},
text: {
type: "document",
value: "Hello! Welcome to our newsletter."
}
},
postal_address: "123 Main St, Minneapolis, MN 55401"
}]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
broadcasts: [{
name: "December Newsletter",
subject: "Your December Newsletter is here!",
preheader: "See what's new this month...",
content: {
html: {
type: "document",
value: "<html><body><h1>Hello!</h1></body></html>"
},
text: {
type: "document",
value: "Hello! Welcome to our newsletter."
}
},
postal_address: "123 Main St, Minneapolis, MN 55401"
}]
})
}
);
const body = await response.json();
Responds with a
201 Createdand the new Single-Email Campaign if successful. TheLocationheader contains the URL of the created record:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If the request is invalid, responds with a
422 Unprocessable Entity:
{
"errors": [{
"code": "validation_error",
"attribute": "subject",
"message": "Subject is required"
}]
}
Single-Email Campaigns created via the API start in the draft status, with an audience of
everyone eligible to receive them. Set the audience to narrow it, then
schedule it.
POST /v2/:account_id/broadcasts
| Key | Description |
|---|---|
name |
Required. The private name given to the Single-Email Campaign (255 characters maximum). |
subject |
Required. The email subject line. Supports Liquid templating. |
content |
Required. An Email Content object. At minimum, include an html key. Optionally include a text key for the plain text version; if omitted, the plain text version is generated from the HTML. |
preheader |
Optional. Preview text shown in email clients before the email is opened. Supports Liquid templating. |
postal_address |
Optional. A physical mailing address for CAN-SPAM compliance. Defaults to the account's postal address. |
from_name |
Optional. The name the email is sent from (255 characters maximum). Supports Liquid templating. Defaults to the account's default From name. |
from_email_address_id |
Optional. The id of a verified address in the account's sending addresses to send from. Defaults to the account's default From address. See Sender. |
reply_to_email_address_id |
Optional. The id of a verified, non-auto-generated address in the account's sending addresses to receive replies. If omitted, replies go to the From address. See Sender. |
To fetch a specific Single-Email Campaign:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
broadcast_id = "BROADCAST_ID"
response = client.broadcast(broadcast_id)
if response.success?
puts response.body
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const broadcastId = "BROADCAST_ID";
client.fetchBroadcast(broadcastId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The broadcasts property is an array of one broadcast object.
{
"links": { ... },
"broadcasts": [{ ... }]
}
GET /v2/:account_id/broadcasts/:broadcast_id
None.
To update a Single-Email Campaign:
curl -X PATCH "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"broadcasts": [{
"subject": "Updated: December Newsletter!",
"preheader": "New content inside...",
"content": {
"html": {
"type": "document",
"value": "<html><body><h1>Updated content!</h1></body></html>"
}
}
}]
}
EOF
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID")
request = Net::HTTP::Patch.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/json"
request.body = {
broadcasts: [{
subject: "Updated: December Newsletter!",
preheader: "New content inside...",
content: {
html: {
type: "document",
value: "<html><body><h1>Updated content!</h1></body></html>"
}
}
}]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
broadcasts: [{
subject: "Updated: December Newsletter!",
preheader: "New content inside...",
content: {
html: {
type: "document",
value: "<html><body><h1>Updated content!</h1></body></html>"
}
}
}]
})
}
);
const body = await response.json();
Responds with a
200 OKand the updated Single-Email Campaign if successful:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If the Single-Email Campaign is not a draft, responds with a
409 Conflict:
{
"errors": [{
"code": "conflict_error",
"message": "Cannot update a broadcast with status 'sent'"
}]
}
Only Single-Email Campaigns in the draft status can be updated via the API; all other
statuses are read-only. Updates are partial: only the fields you include are changed. To change
the From name, From address or Reply-To address, see Sender.
Status, scheduling, and the audience are not updated here. Each has its own endpoint: Set the audience, Schedule, and Unschedule.
PATCH /v2/:account_id/broadcasts/:broadcast_id
| Key | Description |
|---|---|
name |
Optional. The private name given to the Single-Email Campaign. |
subject |
Optional. The email subject line. Supports Liquid templating. |
content |
Optional. An Email Content object with html and/or text keys. |
preheader |
Optional. Preview text shown in email clients before the email is opened. Supports Liquid templating. |
postal_address |
Optional. A physical mailing address for CAN-SPAM compliance. |
from_name |
Optional. The name the email is sent from (255 characters maximum). Supports Liquid templating. |
from_email_address_id |
Optional. The id of a verified address in the account's sending addresses to send from. Send null or "" to use the account's default From address. See Sender. |
reply_to_email_address_id |
Optional. The id of a verified, non-auto-generated address in the account's sending addresses to receive replies. Send null or "" to have replies go to the From address. See Sender. |
To delete a Single-Email Campaign:
curl -X DELETE "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID")
request = Net::HTTP::Delete.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID",
{
method: "DELETE",
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
Responds with a
200 OKand the Single-Email Campaign in thedeletedstatus:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If the Single-Email Campaign is currently sending, responds with a
409 Conflict:
{
"errors": [{
"code": "conflict_error",
"message": "Cannot delete a broadcast that is currently sending"
}]
}
Deleting is a soft delete: the Single-Email Campaign transitions to the deleted status and
is hidden from default listings, but historical data is preserved. Single-Email Campaigns in
the sending status cannot be deleted.
DELETE /v2/:account_id/broadcasts/:broadcast_id
None.
To send a test email for a Single-Email Campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/send_test" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"to_emails": ["[email protected]"],
"preview_as": "[email protected]"
}
EOF
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/send_test")
request = Net::HTTP::Post.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/json"
request.body = {
to_emails: ["[email protected]"],
preview_as: "[email protected]"
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.code
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/send_test",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
to_emails: ["[email protected]"],
preview_as: "[email protected]"
})
}
);
// 204 No Content on success
Responds with a
204 No Contentif successful.If a recipient is not allowed, responds with a
422 Unprocessable Entity:
{
"errors": [{
"code": "validation_error",
"attribute": "to_emails",
"message": "[email protected] is not a verified domain or active account member"
}]
}
If a rate limit is exceeded, responds with a
429 Too Many Requestsand aRetry-Afterheader indicating when the limit resets:
{
"errors": [{
"code": "rate_limit_error",
"message": "Hourly test email limit exceeded. Maximum 20 requests per hour."
}]
}
Sends a test email so you can preview how the Single-Email Campaign will appear to recipients. Test emails are marked with a "[TEST]" prefix in the from name, and any links that would trigger automations are stripped for safety. If sending is blocked on the account (for example, during a trial), tests are only delivered to the authenticated user.
Each recipient email address must satisfy one of the following requirements:
example.com is verified, [email protected] is allowed).Recipients that do not meet either requirement are rejected with a 422 validation error.
Rate limits are applied per user.
| Limit | Window |
|---|---|
| 5 recipients | Per request |
| 20 requests | Per hour |
| 40 requests | Per day |
POST /v2/:account_id/broadcasts/:broadcast_id/send_test
| Key | Description |
|---|---|
to_emails |
Required. An array of 1 to 5 email addresses to send the test to. See recipient validation above. |
preview_as |
Optional. The email address of an existing subscriber to use for Liquid template rendering. The test email is personalized as if sent to this subscriber, using their custom fields and tags. If omitted, Liquid variables use placeholder values. |
To send a Single-Email Campaign to a saved segment:
curl -X PUT "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience" \
-H "Content-Type: application/vnd.api+json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d '{
"filter": [
[{ "type": "segments", "properties": { "operator": "in", "segment_id": "4815162" } }]
]
}'
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience")
request = Net::HTTP::Put.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/vnd.api+json"
request.body = {
filter: [
[{ type: "segments", properties: { operator: "in", segment_id: "4815162" } }]
]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience",
{
method: "PUT",
headers: {
"Content-Type": "application/vnd.api+json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
filter: [
[{ type: "segments", properties: { operator: "in", segment_id: "4815162" } }]
]
})
}
);
const body = await response.json();
Responds with a
200 OKand the Single-Email Campaign:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If a condition names a segment the account cannot use, responds with a
404 Not Found:
{
"errors": [{
"code": "not_found_error",
"attribute": "/filter/0/0/properties/segment_id",
"message": "No active segment with id '4815162' was found on this account"
}]
}
If the filter is malformed, responds with a
422 Unprocessable Entity, naming every problem at once:
{
"errors": [{
"code": "unknown_operator",
"attribute": "/filter/0/0/properties/operator",
"message": "Unknown operator 'within' for condition type 'segments'. Valid operators: in, not_in"
}]
}
If the campaign already has an audience and
confirm=truewas not sent, responds with a409 Conflict:
{
"errors": [{
"code": "confirmation_required",
"attribute": "confirm",
"message": "This broadcast has a filter audience (People who are in the \"VIP customers\" segment) that will be replaced and cannot be recovered. Retry with confirm=true to proceed."
}]
}
Replaces who the Single-Email Campaign goes to. Only a draft campaign's audience can be
changed; anything else responds with a 409 Conflict.
A filter is a list of AND-sets, combined with OR. Everything inside one inner list has to be
true of a person, and a person matching any inner list is included. So
[[A, B], [C]] means "(A and B) or C".
[[]] means everyone eligible to receive it, and is how you clear an audience. An empty
filter, [], is rejected.
Today the only condition type available is segments, with the operators in and
not_in. The segment_id is the id from the Segments API. More condition types will follow.
segment_id takes one id, never a list: ["4815162"] is rejected. For several segments, use
one condition each. Two conditions in the same inner list mean "in both"; two in separate
inner lists mean "in either".
Replacing an audience requires confirm=true whenever the current audience is anything other
than everyone, because what is being replaced cannot be recovered through the API. Read the
audience first if you need to keep it.
Every problem with a filter is reported in a single response. Each error's attribute is a
JSON Pointer to the condition that caused it, so you can fix a request once rather than once
per round trip.
An audience set here appears in the Drip app as the campaign's recipients, the same as one chosen there.
PUT /v2/:account_id/broadcasts/:broadcast_id/audience
| Argument | Description |
|---|---|
| filter | Required. A list of AND-sets of conditions. Send [[]] for everyone. |
| confirm | Optional. Set to true to acknowledge that the current audience will be replaced. Required whenever the current audience is anything other than everyone. |
To read a Single-Email Campaign's audience:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience")
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/audience",
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
Responds with a
200 OK:
{
"audience": {
"filter": [
[{ "type": "segments", "properties": { "operator": "in", "segment_id": "4815162" } }]
]
}
}
When the audience uses rules this API cannot yet express:
{
"audience": {
"message": "This filter includes unsupported criteria and cannot be returned as a filter.",
"summary": "People who are tagged with \"vip\"",
"unsupported_types": ["tags"]
}
}
filter comes back in the same shape it is set in, so reading an audience and writing it
straight back changes nothing.
filter is present only when writing it again would be accepted. Most audiences built in the
Drip app use rules that have no form here yet, and those come back described instead:
summary is plain-English display text, and unsupported_types names the condition types in
the way. Expect that until more condition types are available.
A present filter is not a promise that its segments still exist. That is not checked on
read, so an audience naming a segment deleted since it was set reads back intact and responds
with a 404 Not Found when written again.
GET /v2/:account_id/broadcasts/:broadcast_id/audience
None.
To send a Single-Email Campaign at 9:00 in each person's own time zone:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/schedule" \
-H "Content-Type: application/vnd.api+json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d '{
"send_at": "2026-11-01T09:00:00Z",
"localize_sending_time": true
}'
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/schedule")
request = Net::HTTP::Post.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
request["Content-Type"] = "application/vnd.api+json"
request.body = {
send_at: "2026-11-01T09:00:00Z",
localize_sending_time: true
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/schedule",
{
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
},
body: JSON.stringify({
send_at: "2026-11-01T09:00:00Z",
localize_sending_time: true
})
}
);
const body = await response.json();
Responds with a
200 OKand the Single-Email Campaign in thescheduledstatus:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If the request is malformed, responds with a
422 Unprocessable Entity, naming every problem at once:
{
"errors": [{
"code": "validation_error",
"attribute": "localize_sending_time",
"message": "localize_sending_time is required with send_at: false sends at the instant given, true sends at that wall clock in each person's own zone."
}]
}
If the campaign cannot be scheduled in its current state, responds with a
409 Conflict:
{
"errors": [{
"code": "conflict_error",
"message": "This broadcast is already scheduled. Unschedule it first to change when it sends."
}]
}
Sets a draft Single-Email Campaign to send. There are three ways to say when, and
localize_sending_time is required alongside send_at rather than defaulting, because the two
readings of the same timestamp differ by hours for most people:
| Request | Sends |
|---|---|
send_at, localize_sending_time: false |
At the instant given, honoring its offset. |
send_at, localize_sending_time: true |
At that time of day in each person's own time zone. |
send_at with an offset, localize_sending_time: true |
The same: the offset is dropped and only the time of day is used. |
So 2026-11-01T09:00:00-05:00 with localize_sending_time: true reaches everyone at 9:00 their
own time, not at 14:00 UTC. People with no time zone on record receive a localized send at that
time of day in UTC.
To send now, pass send_immediately: true instead of send_at. Send exactly one of the two,
and do not combine send_immediately with localize_sending_time.
A send_at must be at least five minutes away, or at least 14 hours and five minutes away when
localized, so that every time zone can still be reached. Localized sending cannot be used with a split test
whose pool is less than 100%.
Only a draft can be scheduled. To change when a scheduled campaign sends, unschedule it first,
then schedule it again. A campaign that is already scheduled, sending, or sent, or whose account
cannot send right now, responds with a 409 Conflict that says which.
The audience is not checked here. A campaign with no audience set goes to everyone eligible to
receive it, the same as in the Drip app. Read derived.audience first if that matters.
With auto_resend, a second copy goes to people who did not open the first, resend_delay
whole days later.
POST /v2/:account_id/broadcasts/:broadcast_id/schedule
| Argument | Description |
|---|---|
| send_at | An ISO 8601 timestamp, such as 2026-11-01T09:00:00Z. Required unless send_immediately is true. |
| localize_sending_time | Required with send_at. false sends at the instant given; true sends at that time of day in each person's own time zone. |
| send_immediately | Optional. Set to true to send now instead of at send_at. Defaults to false. |
| auto_resend | Optional. Set to true to send a second copy to people who did not open the first. Defaults to false. |
| resend_subject | Optional. The subject for the resend. Defaults to the campaign's own subject. |
| resend_delay | Required when auto_resend is true. Whole days to wait before the resend, at least 1. |
To return a scheduled Single-Email Campaign to draft:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/unschedule" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/unschedule")
request = Net::HTTP::Post.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/broadcasts/BROADCAST_ID/unschedule",
{
method: "POST",
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
Responds with a
200 OKand the Single-Email Campaign in thedraftstatus:
{
"links": { ... },
"broadcasts": [{ ... }]
}
If the Single-Email Campaign has already sent, responds with a
409 Conflict:
{
"errors": [{
"code": "conflict_error",
"message": "Cannot unschedule a broadcast with status 'sent'"
}]
}
Takes a scheduled Single-Email Campaign back to draft so it will not send. The scheduled
send time is not preserved, so scheduling it again means setting the time again.
Calling this on a campaign that is already a draft succeeds and changes nothing, so a retried call is safe. A campaign that is sending or has sent cannot be unscheduled.
POST /v2/:account_id/broadcasts/:broadcast_id/unschedule
None.
Email Series Campaigns are represented as follows:
{
"id": "123456",
"status": "active",
"name": "SEO Email Course",
"from_name": "John Doe",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"postal_address": "123 Anywhere St\nFresno, CA 99999",
"minutes_from_midnight": 440,
"localize_sending_time": true,
"days_of_the_week_mask": "0111110",
"start_immediately": true,
"double_optin": true,
"send_to_confirmation_page": false,
"use_custom_confirmation_page": false,
"confirmation_url": null,
"notify_subscribe_email": "[email protected]",
"notify_unsubscribe_email": "[email protected]",
"bcc": null,
"email_count": 10,
"active_subscriber_count": 320,
"unsubscribed_subscriber_count": 5,
"created_at": "2013-06-21T10:31:58Z",
"href": "https://api.getdrip.com/v2/9999999/campaigns/123456",
"links": {
"account": "9999999",
"forms": ["888"]
}
}
All responses containing Email Series Campaign data also include the following top-level link data:
{
"links": {
"campaigns.account": "https://api.getdrip.com/v2/accounts/{campaigns.account}",
"campaigns.form": "https://api.getdrip.com/v2/{campaigns.account}/forms/{campaigns.forms}",
"campaigns.subscribers": "https://api.getdrip.com/v2/{campaigns.account}/campaigns/{campaigns.id}/subscribers"
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each Email Series Campaign record. |
status |
Returns whether the Email Series Campaign is active, paused or in draft. |
name |
The private name given to the Email Series Campaign. |
from_name |
A "from name" that appears in your sent emails. This setting overrides the account's default from name. |
from_email |
A "from email" that appears in your sent emails. This setting overrides the account's default from email. |
reply_to |
The Reply-To address set on the Email Series Campaign, or null if none is set. When it is null, replies go to the From address. |
postal_address |
As required by the CAN-SPAM Act, this is a postal address used for all sent emails. |
minutes_from_midnight |
The number of minutes after midnight for the time of day set for email sending. |
localize_sending_time |
The scheduled send_at time if set to be sent in the subscriber's time zone. |
days_of_the_week_mask |
A representation of the days of week when emails are set to be sent. For example, 1111100 represents sending enabled only for Mondays to Fridays while 1111111 represents sending enabled for all days of the week. |
start_immediately |
Returns true if the first email in the Email Series Campaign is set to be delivered immediately after a Email Series Campaign subscription. |
double_optin |
Returns true if double opt-in is enabled for the Email Series Campaign. |
send_to_confirmation_page |
Deprecated |
use_custom_confirmation_page |
Deprecated |
post_confirmation_url |
Deprecated |
notify_subscribe_email |
An email address set that receives a notification whenever a subscriber subscribes via a form submission. |
notify_unsubscribe_email |
An email address set that receives a notification whenever a subscriber unsubscribes via their subscription management page. |
bcc |
A blind copy email address set for all Email Series Campaign email deliveries. |
email_count |
Returns a count of all emails associated with the Email Series Campaign. Includes all email statuses. |
active_subscriber_count |
Returns a count of all subscribers who are actively subscribed to the Email Series Campaign. |
unsubscribed_subscriber_count |
Returns a count of all subscribers who unsubscribed from the Email Series Campaign via a delivered email. |
created_at |
A timestamp representing when the Email Series Campaign was first created. |
href |
The url designated for retrieving the Email Series Campaign record via the REST API. |
links |
An object containing the REST API URL for the account, any associated Email Series Campaign forms and subscribers subscribed to the Email Series Campaign. |
forms |
An object containing the associated form created for the Email Series Campaign. This is only populated if a form is created for the Email Series Campaign. Refer to Forms for an overview of the properties returned here. |
To list all Email Series Campaigns:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.campaigns
if response.success?
puts response.body["campaigns"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { status: "active" };
client.listCampaigns(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The campaigns property is an array of campaign objects.
{
"links": { ... },
"meta": {
"page": 1,
"sort": "created_at",
"direction": "asc",
"count": 5,
"total_pages": 1,
"total_count": 5,
"status": "all"
},
"campaigns": [ ... ]
}
GET /v2/:account_id/campaigns
| Key | Description |
|---|---|
status |
Optional. Filter by one of the following statuses: draft, active, or paused. Defaults to all. |
sort |
Optional. Sort results by one of these fields: created_at or name. Defaults to created_at. |
direction |
Optional. Filter sort direction with: asc or desc. Defaults to asc. |
To fetch a specific Email Series Campaign:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns/CAMPAIGN_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
campaign_id = 9999999
response = client.campaign(campaign_id)
if response.success?
puts response.body
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const campaignId = 9998888;
client.fetchCampaign(campaignId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The campaigns property is an array of campaign objects.
# The forms property is an array of forms that feed directly into to the campaign.
{
"links": { ... },
"campaigns": [{ ... }],
"linked": {
"forms": [{ ... }]
}
}
GET /v2/:account_id/campaigns/:campaign_id
None.
To activate an Email Series Campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns/CAMPAIGN_ID/activate" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
campaign_id = 9999999
response = client.activate_campaign(campaign_id)
if response.success?
puts response.body
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const campaignId = 9998888;
client.activateCampaign(campaignId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful. If the Email Series Campaign cannot be activated, returns a422 Unprocessable Entity.
POST /v2/:account_id/campaigns/:campaign_id/activate
None.
To pause an Email Series Campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns/CAMPAIGN_ID/pause" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
campaign_id = 9999999
response = client.pause_campaign(campaign_id)
if response.success?
puts response.body
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const campaignId = 9998888;
client.pauseCampaign(campaignId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful. If the Email Series Campaign cannot be paused, returns a422 Unprocessable Entity.
POST /v2/:account_id/campaigns/:campaign_id/pause
None.
To list subscribers on an Email Series Campaign:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns/CAMPAIGN_ID/subscribers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
campaign_id = 9999999
response = client.campaign_subscribers(campaign_id)
if response.success?
puts response.body["subscribers]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const campaignId = 9998888;
client.listAllSubscribesToCampaign(campaignId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of subscriber objects.
{
"links": { ... },
"meta": {
"page": 1,
"direction": "desc",
"count": 20,
"total_pages": 1,
"total_count": 20
},
"subscribers": [ ... ]
}
GET /v2/:account_id/campaigns/:campaign_id/subscribers
| Key | Description |
|---|---|
status |
Optional. The status to filter by: active, unsubscribed, or removed. Defaults to active. |
page |
Optional. The page number. Defaults to 1. |
direction |
Optional. The direction to sort the results for created_at: asc or desc. Defaults to desc. |
per_page |
Optional. The number of records to be returned on each page. Defaults to 100. Maximum 1000. |
To start a subscriber on an Email Series Campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/campaigns/CAMPAIGN_ID/subscribers" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"subscribers": [{
"email": "[email protected]",
"utc_offset": 660,
"double_optin": true,
"starting_email_index": 0,
"reactivate_if_removed": true,
"custom_fields": {
"shirt_size": "Medium"
}
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
campaign_id = 9999999
email = "[email protected]"
options = {
time_zone: "America/Los_Angeles",
custom_fields: {
name: "Jane Doe"
}
}
response = client.subscribe(email, campaign_id, options)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const campaignId = 9998888;
const payload = {
subscribers: [{
email: "[email protected]",
time_zone: "Asia/Kuala_Lumpur",
custom_fields: {
name: "Jane Doe"
}
}]
}
client.subscribeToCampaign(campaignId, payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
POST /v2/:account_id/campaigns/:campaign_id/subscribers
| Key | Description |
|---|---|
email |
Required. The subscriber's email address. |
user_id |
Optional. A unique identifier for the user in your database, such as a primary key. |
time_zone |
Optional. The subscriber's time zone (in Olson format). Defaults to Etc/UTC |
double_optin |
Optional. If true, the double opt-in confirmation email is sent; if false, the confirmation email is skipped. Defaults to the value set on the Email Series Campaign. |
starting_email_index |
Optional. The index (zero-based) of the email to send first. Defaults to 0. |
custom_fields |
Optional. An Object containing custom field data. E.g. { "shirt_size": "Medium" }. |
tags |
Optional. An Array containing one or more tags. E.g. ["Customer", "SEO"]. |
reactivate_if_removed |
Optional. If true, re-subscribe the subscriber to the Email Series Campaign if there is a removed subscriber in Drip with the same email address; otherwise, respond with 422 Unprocessable Entity. Defaults to true. |
prospect |
Optional. A Boolean specifiying whether we should attach a lead score to the subscriber (when lead scoring is enabled). Defaults to true.
Note: This flag used to be called potential_lead, which we will continue to accept for backwards compatibility. |
base_lead_score |
Optional. An Integer specifying the starting value for lead score calculation for this subscriber. Defaults to 30. |
eu_consent |
Optional. A String specifying whether the subscriber granted or denied GDPR consent. |
eu_consent_message |
Optional. A String containing the message the subscriber granted or denied their consent to. |
To list Email Series Campaign subscriptions for a subscriber:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/SUBSCRIBER_ID/campaign_subscription" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_id = "bihfwo84teh35dgt99"
response = client.campaign_subscriptions(subscriber_id)
if response.success?
puts response.body["campaign_subscriptions"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const subscriberId = "iuyfgfweufgr9hvrugegff";
client.subscriberCampaignSubscriptions(subscriberId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The Email Series Campaign subscriptions property is an array of campaign subscription objects.
{
"links": {
"campaign_subscriptions.account": "https://api.getdrip.com/v2/accounts/{campaign_subscriptions.account}",
"campaign_subscriptions.subscriber": "https://api.getdrip.com/v2/subscribers/{campaign_subscriptions.subscriber}"
},
"meta": {
"page": 1,
"count": 5,
"total_pages": 1,
"total_count": 5
},
"campaign_subscriptions": [{
"id": "123456",
"campaign_id": "999999",
"status": "active",
"is_complete": false,
"lap": 1,
"last_sent_email_index": 0,
"last_sent_email_at": "2016-03-25T11:00:00Z",
"links": {
"account": "9999999",
"subscriber": "z1togz2hcjrkpp5treip"
}
}]
}
GET /v2/:account_id/subscribers/:subscriber_id/campaign_subscriptions
None.
To list all custom fields:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/custom_field_identifiers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.custom_fields
if response.success?
puts response.body["custom_field_identifiers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
client.listAllCustomFields()
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"custom_field_identifiers": [ "first_name", "last_name" ]
}
Properties
| Property | Description |
|---|---|
custom_field_identifiers |
Returns a list of all active custom field identifiers used in the target account. |
GET /v2/:account_id/custom_field_identifiers
None.
Conversions are represented as follows:
{
"id": "99999",
"status": "active",
"name": "Trial Signup",
"url": "/receipt",
"default_value": 2000,
"counting_method": "one_per_visitor",
"created_at": "2013-06-21T10:31:58Z",
"href": "https://api.getdrip.com/v2/9999999/goals/99999",
"links": {
"account": "9999999"
}
}
All responses containing conversion data also include the following top-level link data:
{
"links": {
"goals.account": "https://api.getdrip.com/v2/accounts/{goals.account}"
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each conversion record. |
status |
Returns whether the conversion is enabled or disabled. |
name |
The private name given to the conversion. |
url |
A URL used for detecting and recording conversions. |
default_value |
A default value assigned to a tracked conversion. Conversion values must be less than 2,147,483,647. |
counting_method |
Set either as one_per_visitor or all and determines whether a maximum of one conversion is counted per person or all. |
created_at |
A timestamp representing when the conversion was first created. |
href |
The url designated for retrieving the conversion record via the REST API. |
links |
An object containing the REST API URL for the account. |
See the Events API for recording conversion events.
To list all conversions in an account:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/goals" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.conversions
if response.success?
puts response.body["goals"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { status: "active" };
client.listConversions(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The goals property is an array of conversion goal objects.
{
"links": { ... },
"goals": [ ... ]
}
GET /v2/:account_id/goals
| Key | Description |
|---|---|
status |
Optional. The status to filter by: active, disabled, or all. Defaults to all. |
sort |
Optional. Sort results by one of these fields: created_at or name. Defaults to created_at. |
direction |
Optional. Filter sort direction with: asc or desc. Defaults to asc. |
To fetch a conversion:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/goals/CONVERSION_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
conversion_id = 9999999
response = client.conversion(conversion_id)
if response.success?
puts response.body["goals"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const conversionId = 8889999;
client.fetchConversion(conversionId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The goals property is an array of one conversion goal object.
{
"links": { ... },
"goals": [{ ... }]
}
GET /v2/:account_id/goals/:conversion_id
None.
To create or update a subscriber:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/events" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"events": [{
"email": "[email protected]",
"action": "Logged in",
"properties": {
"affiliate_code": "XYZ"
},
"occurred_at": "2014-03-22T03:00:00Z"
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
email = "[email protected]"
action = "Docked with space station"
properties = {
station: "Mars Endeavor"
}
response = client.track_event(email, action, properties)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const payload = {
email: "[email protected]",
action: "Logged in",
properties: {
affiliate_code: "XYZ"
}
};
client.recordEvent(payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
If you need to create or update a collection of events at once, use the Batch API instead.
POST /v2/:account_id/events
| Key | Description |
|---|---|
email |
Optional. The subscriber's email address. Either email or id must be included. |
id |
Optional. The subscriber's Drip id. Either email or id must be included. |
action |
Required. The name of the action taken. E.g. "Logged in" |
prospect |
Optional. A Boolean specifiying whether we should attach a lead score to the subscriber (when lead scoring is enabled). Defaults to true.
Note: This flag used to be called potential_lead, which we will continue to accept for backwards compatibility. |
properties |
Optional. An Object containing custom event properties. If this event is a conversion, include the value (in cents) in the in the properties with a value key.
Conversion values must be less than 2,147,483,647.
|
occurred_at |
Optional. The String time at which the event occurred in ISO-8601 format. Defaults to the current time. |
To list custom event actions:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/event_actions" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.event_actions
if response.success?
puts response["event_actions"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { per_page: 200 };
client.listEventActions(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"meta": {
"count": 2,
"page": 1,
"total_count": 2,
"total_pages": 1
},
"event_actions": [
"Ate a sandwich",
"Started a trial"
]
}
GET /v2/:account_id/event_actions
| Key | Description |
|---|---|
page |
Optional. The page number. Defaults to 1. |
per_page |
Optional. The number of records to be returned on each page. Defaults to 100. Maximum 1000. |
Forms are represented as follows:
{
"id": "77777",
"href": "https://api.getdrip.com/v2/9999999/forms/77777",
"headline": "Long Tail SEO Course",
"description": "Get our FREE email course",
"button_text": "Sign Up!",
"confirmation_heading": "Thank you for subscribing",
"confirmation_text": "Please click the link in your email",
"send_ga_event": true,
"seconds_before_popup": 5,
"days_between_popup": 5,
"days_between_popup_after_close": 10,
"orientation": "bottom_right_tab",
"opacity": 0.8,
"show_labels": false,
"primary_color": "#333333",
"secondary_color": "#d8ab93",
"is_widget_enabled": true,
"whitelist": [ "/landing", "/public" ],
"blacklist": [],
"is_whitelist_enabled": true,
"is_blacklist_enabled": false,
"hide_on_mobile": false,
"is_embeddable": true,
"created_at": "2013-06-21T10:31:58Z",
"links": {
"account": "9999999"
}
}
All responses containing form data also include the following top-level link data:
{
"links": {
"forms.account": "https://api.getdrip.com/v2/accounts/{forms.account}"
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each form record. |
href |
The url designated for retrieving the form record via the REST API. |
headline |
The visible headline on the form widget or embedded form. |
description |
A description shown in the body of the form widget or in the embedded form. |
button_text |
The text shown on the submission button for the form. |
confirmation_heading |
A heading shown after a subscriber submits the form. |
confirmation_text |
The text shown after a subscriber submits the form. |
send_ga_event |
Returns true if an event is sent to Google Analytics upon submission. |
seconds_before_popup |
A delay in seconds before the form widget is displayed. |
days_between_popup |
A delay in days between each appearance of the form widget. |
days_between_popup_after_close |
A delay in days between each appearance of the form widget after a visitor closes the form. |
orientation |
The position on the page where the form widget is displayed. Possible values are bottom_left_tab, bottom_right_tab, side_left_tab, side_right_tab, embedded or lightbox. |
opacity |
The opacity set for form field labels. |
show_labels |
Returns true if form field labels are shown above text boxes. |
primary_color |
The tab color for the form. |
secondary_color |
The headline color for the form. |
is_widget_enabled |
Returns true if the form widget is enabled and shown. |
whitelist |
An array of URL paths where the form is shown. |
blacklist |
An array of URL paths where the form is hidden. |
is_whitelist_enabled |
Returns true if whitelisted URLs are set. |
is_blacklist_enabled |
Returns true if blacklisted URLs are set. |
hide_on_mobile |
Returns true if the form widget is disabled for mobile devices. |
is_embeddable |
Deprecated |
created_at |
A timestamp representing when the form was first created. |
links |
An object containing the REST API URL for the account. |
To list all forms in an account:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/forms" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.forms
if response.success?
puts response.body["forms"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
client.listForms()
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The forms property is an array of form objects.
{
"links": { ... },
"forms": [ ... ]
}
GET /v2/:account_id/forms
None.
To fetch a specific form:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/forms/FORM_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
form_id = 9999999
response = client.form(form_id)
if response.success?
puts response.body["forms"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const formId = 9998888;
client.fetchForm(formId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The forms property is an array of one form object.
{
"links": { ... },
"forms": [{ ... }]
}
GET /v2/:account_id/forms/:form_id
None.
The Metrics API returns aggregate email performance for an account: sends, deliveries, opens, clicks, bot clicks, unsubscribes, hard bounces, complaints, orders, and revenue.
Results cover Single-Email Campaigns, and automation emails sent by Workflows. Confirmation emails (double opt-in) are excluded. Order and revenue values are reported in cents.
Dates are interpreted in the account owner's time zone.
Email metrics are represented as follows:
{
"email_id": 4455661,
"broadcast_id": "123456",
"workflow_placement": {
"workflow_id": "789012",
"node_id": "SENDEMAILNODE1"
},
"title": "December Newsletter",
"email_metric": { ... }
}
Properties
| Property | Description |
|---|---|
email_id |
A read-only Drip generated unique id used to identify each email record. |
broadcast_id |
The id of the Single-Email Campaign that sent the email. Only present for Single-Email Campaign emails. |
workflow_placement |
An object containing the workflow_id of the workflow that sends the email and the node_id of the send-email step within it. Only present when the request includes a workflow_ids filter and the email is sent by one of those workflows. node_id holds the same value Fetch a workflow's details returns as a node's id. |
title |
The name of the Single-Email Campaign, Email Series Campaign, or automation that sent the email. |
email_metric |
An object containing the email's metrics. |
The email metric object structure:
{
"sends": 1000,
"deliveries": 970,
"opens": 420,
"open_rate": 0.42,
"clicks": 85,
"click_rate": 0.085,
"bot_clicks": 12,
"bot_click_rate": 0.012,
"unsubscribes": 7,
"unsubscribe_rate": 0.007,
"hard_bounces": 18,
"hard_bounce_rate": 0.018,
"complaints": 2,
"complaint_rate": 0.002,
"orders": 24,
"order_rate": 0.024,
"revenue_in_cents": 384000,
"revenue_per_person_in_cents": 384,
"average_order_value_in_cents": 16000
}
Every rate is calculated against sends and rounded to four decimal places. The same object is
used for the report summary and for each email's email_metric.
| Key | Description |
|---|---|
sends |
The number of emails sent. |
deliveries |
The number of sent emails accepted by the recipient's mail server. |
opens |
The number of opens recorded. |
open_rate |
opens divided by sends. |
clicks |
The number of clicks recorded. |
click_rate |
clicks divided by sends. |
bot_clicks |
The number of clicks attributed to automated systems rather than people. |
bot_click_rate |
bot_clicks divided by sends. |
unsubscribes |
The number of unsubscribes. |
unsubscribe_rate |
unsubscribes divided by sends. |
hard_bounces |
The number of hard bounces. |
hard_bounce_rate |
hard_bounces divided by sends. |
complaints |
The number of spam complaints. |
complaint_rate |
complaints divided by sends. |
orders |
The number of orders attributed to the email. |
order_rate |
orders divided by sends. |
revenue_in_cents |
The revenue attributed to the email, in cents. |
revenue_per_person_in_cents |
revenue_in_cents divided by sends, in cents. |
average_order_value_in_cents |
revenue_in_cents divided by orders, in cents. |
To fetch email metrics:
curl -g "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/metrics/email?start_date=2026-01-01&end_date=2026-01-31&broadcast_ids[]=BROADCAST_ID&workflow_ids[]=WORKFLOW_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/metrics/email")
uri.query = URI.encode_www_form(
"start_date" => "2026-01-01",
"end_date" => "2026-01-31",
"broadcast_ids[]" => "BROADCAST_ID",
"workflow_ids[]" => "WORKFLOW_ID"
)
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const params = new URLSearchParams({
start_date: "2026-01-01",
end_date: "2026-01-31"
});
params.append("broadcast_ids[]", "BROADCAST_ID");
params.append("workflow_ids[]", "WORKFLOW_ID");
const response = await fetch(
`https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/metrics/email?${params}`,
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
The response looks like this:
# The emails property is an array of email metrics objects.
{
"summary": { ... },
"emails_total": 3,
"emails_truncated": false,
"emails": [ ... ]
}
If the filters match no emails, the summary is zeroed and no emails are returned:
{
"summary": {
"sends": 0,
"deliveries": 0,
...
},
"emails_total": 0,
"emails_truncated": false,
"emails": []
}
If an argument is invalid or exceeds a limit, responds with a
400 Bad Request:
{
"errors": [{
"code": "bad_request",
"message": "Date range cannot exceed 366 days."
}]
}
If the rate limit is exceeded, responds with a
429 Too Many Requestsand aRetry-Afterheader indicating when the limit resets:
{
"errors": [{
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded: at most 20 requests per account per hour."
}]
}
If the report cannot be produced in time, responds with a
503 Service Unavailable:
{
"errors": [{
"code": "unavailable_error",
"message": "Service temporarily unavailable, try again later"
}]
}
The summary totals the report across every email in the range, and emails lists up to
limit of those emails individually (100 by default). Emails are listed by sends, highest
first, with ties broken by email_id in ascending order. emails_total is the number of emails
that matched, and emails_truncated is true when that is more than emails lists. To list the
rest, call again with limit set to emails_total, up to 1,000; beyond that the full list isn't
available. This endpoint is not paginated.
Rule- and form-automation emails appear only in unfiltered results. broadcast_ids reaches only
Single-Email Campaign emails and workflow_ids reaches only Workflow emails, so neither filter
can target them.
| Limit | Applies to |
|---|---|
| 366 days | The date range, after defaults are applied |
| 25 ids | workflow_ids per request |
| 1,000 emails | The emails that broadcast_ids and workflow_ids resolve to |
| 1,000 emails | limit |
Requests that exceed any of these limits are rejected with a 400 Bad Request.
This endpoint has its own rate limit, separate from the standard API rate limit. Limits are applied per account.
| Limit | Window |
|---|---|
| 20 requests | Per hour |
GET /v2/:account_id/metrics/email
| Key | Description |
|---|---|
start_date |
Optional. The first day of the range in YYYY-MM-DD format, inclusive. Defaults to 30 days ago. |
end_date |
Optional. The last day of the range in YYYY-MM-DD format, inclusive. Defaults to today. |
broadcast_ids |
Optional. An Array of Single-Email Campaign ids. Limits results to the emails sent by those Single-Email Campaigns. |
workflow_ids |
Optional. An Array of workflow ids. Limits results to the emails sent by those workflows. May be combined with broadcast_ids, in which case the results are the union of both filters. |
limit |
Optional. The maximum number of emails to list in emails, a whole number from 1 to 1,000. Defaults to 100. Does not change summary or emails_total. |
Orders are represented as follows:
{
"id": "9999999",
"provider": "shopify",
"upstream_id": "abcdef",
"identifier": "Order_123456",
"amount": 4900,
"tax": 100,
"fees": 0,
"discount": 0,
"permalink": "http://myorders.com/orders/123456",
"currency_code": "USD",
"properties": {
"shirt_size": "Medium",
"color": "red"
},
"occurred_at": "2013-06-21T10:31:58Z",
"closed_at": "2013-06-21T10:35:58Z",
"cancelled_at": null,
"financial_state": "paid",
"fulfillment_state": "fulfilled",
"billing_address": {
"name": "Bill Billington",
"first_name": "Bill",
"last_name": "Billington",
"company": "Bills R US",
"address_1": "123 Bill St.",
"address_2": "Apt. B",
"city": "Billtown",
"state": "CA",
"zip": "01234",
"country": "United States",
"phone": "555-555-5555",
"email": "[email protected]"
},
"shipping_address": {
"name": "Ship Shippington",
"first_name": "Ship",
"last_name": "Shipington",
"company": "Shipping 4 Less",
"address_1": "123 Ship St.",
"address_2": "null",
"city": "Shipville",
"state": "CA",
"zip": "01234",
"country": "United States",
"phone": "555-555-5555",
"email": "[email protected]"
},
"items": [{
"id": "8888888",
"product_id": "765432",
"sku": "4444",
"amount": 4900,
"name": "Canoe",
"quantity": 1,
"upstream_id": "hijkl",
"upstream_product_id": "opqrs",
"upstream_product_variant_id": "zyxwv",
"price": 4900,
"tax": 100,
"fees": 0,
"discount": 100,
"taxable": true,
"properties": {
"color": "black"
}
}],
"links": {
"account": "9999999",
"subscriber": "5555555"
}
}
Properties
| Property | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id |
A read-only Drip generated unique id used to identify each order record. | ||||||||||||||||||||||||||||||
provider |
The identifier for the provider from which the order data was received in lower snake cased form. For example, shopify or my_store |
||||||||||||||||||||||||||||||
amount |
The total amount of the order in cents (e.g. if the order was $49.99, set the amount to 4999). |
||||||||||||||||||||||||||||||
tax |
The tax on the order in cents (e.g. if the order was $1.00, set the tax to 100). |
||||||||||||||||||||||||||||||
fees |
The fees on the order in cents (e.g. if the order was $1.00, set the fees to 100). |
||||||||||||||||||||||||||||||
discount |
The discount on the order in cents (e.g. if the order was $5.00, set the discount to 500). |
||||||||||||||||||||||||||||||
permalink |
A URL for the human-readable interface to view the order details. | ||||||||||||||||||||||||||||||
currency_code |
The alphabetic ISO 4217 code for the currency of the purchase. | ||||||||||||||||||||||||||||||
upstream_id |
A unique, internal id for the order (generally the primary key generated by the order management system). | ||||||||||||||||||||||||||||||
identifier |
A unique, external identifier for the order. This will be displayed in the Drip UI and should correspond to what a customer sees on invoices, email confirmations, etc. | ||||||||||||||||||||||||||||||
properties |
An Object containing properties about the order. E.g. { "size": "large" } |
||||||||||||||||||||||||||||||
occurred_at |
The String time at which the order occurred in ISO-8601 format. Defaults to the current time. | ||||||||||||||||||||||||||||||
closed_at |
The String time at which the order closed in ISO-8601 format. | ||||||||||||||||||||||||||||||
cancelled_at |
The String time at which the order was cancelled in ISO-8601 format. For an order that was not cancelled, this will be nil. |
||||||||||||||||||||||||||||||
financial_state |
The financial status of the order. One of the following values: pending, authorized, partially_paid, paid, partially_refunded, refunded, voided. |
||||||||||||||||||||||||||||||
fulfillment_state |
The fulfillment status of the order. One of the following values: not_fulfilled, partially_fulfilled, fulfilled. |
||||||||||||||||||||||||||||||
billing_address |
An object containing billing address information.
|
||||||||||||||||||||||||||||||
shipping_address |
An object containing shipping address information.
|
||||||||||||||||||||||||||||||
items |
An Array of objects containing information about specific order items.
|
||||||||||||||||||||||||||||||
links |
An object containing the REST API URL for the account. |
To create or update an order for a subscriber:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
order = {
provider: "shopify",
upstream_id: "abcdef",
identifier: "Order_123456",
amount: 4900,
tax: 100,
fees: 0,
discount: 0,
permalink: "http://myorders.com/orders/123456",
currency_code: "USD",
properties: {
shirt_size: "Medium",
color: "red"
},
occurred_at: "2013-06-21T10:31:58Z",
closed_at: "2013-06-21T10:35:58Z",
financial_state: "paid",
fulfillment_state: "fulfilled",
billing_address: {
name: "Bill Billington",
first_name: "Bill",
last_name: "Billington",
company: "Bills R US",
address_1: "123 Bill St.",
address_2: "Apt. B",
city: "Billtown",
state: "CA",
zip: "01234",
country: "United States",
phone: "555-555-5555",
email: "[email protected]"
},
shipping_address: {
name: "Ship Shippington",
first_name: "Ship",
last_name: "Shipington",
company: "Shipping 4 Less",
address_1: "123 Ship St.",
address_2: "null",
city: "Shipville",
state: "CA",
zip: "01234",
country: "United States",
phone: "555-555-5555",
email: "[email protected]"
},
items: [{
id: "8888888",
product_id: "765432",
sku: "4444",
amount: 4900,
name: "Canoe",
quantity: 1,
upstream_id: "hijkl",
upstream_product_id: "opqrs",
upstream_product_variant_id: "zyxwv",
price: 4900,
tax: 100,
fees: 0,
discount: 100,
taxable: true,
properties: {
color: "black"
}
}]
}
response = client.create_or_update_order("[email protected]", order)
if response.success?
# ...
end
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/orders" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-H "Content-Type: application/json" \
-u YOUR_API_KEY: \
-d @- << EOF
{
"orders": [{
"email": "[email protected]",
"provider": "shopify",
"upstream_id": "abcdef",
"identifier": "Order_123456",
"amount": 4900,
"tax": 100,
"fees": 0,
"discount": 0,
"permalink": "http://myorders.com/orders/123456",
"currency_code": "USD",
"properties": {
"shirt_size": "Medium",
"color": "red"
},
"occurred_at": "2013-06-21T10:31:58Z",
"closed_at": "2013-06-21T10:35:58Z",
"financial_state": "paid",
"fulfillment_state": "fulfilled",
"billing_address": {
"name": "Bill Billington",
"first_name": "Bill",
"last_name": "Billington",
"company": "Bills R US",
"address_1": "123 Bill St.",
"address_2": "Apt. B",
"city": "Billtown",
"state": "CA",
"zip": "01234",
"country": "United States",
"phone": "555-555-5555",
"email": "[email protected]"
},
"shipping_address": {
"name": "Ship Shippington",
"first_name": "Ship",
"last_name": "Shipington",
"company": "Shipping 4 Less",
"address_1": "123 Ship St.",
"address_2": "null",
"city": "Shipville",
"state": "CA",
"zip": "01234",
"country": "United States",
"phone": "555-555-5555",
"email": "[email protected]"
},
"items": [{
"id": "8888888",
"product_id": "765432",
"sku": "4444",
"amount": 4900,
"name": "Canoe",
"quantity": 1,
"upstream_id": "hijkl",
"upstream_product_id": "opqrs",
"upstream_product_variant_id": "zyxwv",
"price": 4900,
"tax": 100,
"fees": 0,
"discount": 100,
"taxable": true,
"properties": {
"color": "black"
}
}]
}]
}
EOF
Responds with a
202 Acceptedand an empty JSON response:
{}
When an order is created, the subscriber's lifetime_value attribute
will be automatically incremented by the total amount of the order.
To update an existing order, include the provider and upstream_id for that order in the payload.
Note: A matching subscriber record must already be present in Drip before an order can be created.
POST /v2/:account_id/orders
| Property | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
email |
Optional. The email address of the order's subscriber. Either email or id must be included. |
||||||||||||||||||||||||||||||
id |
Optional. The Drip id of the order's subscriber. Either email or id must be included. |
||||||||||||||||||||||||||||||
amount |
Required. The total amount of the order in cents (e.g. if the order was $49.99, set the amount to 4999). |
||||||||||||||||||||||||||||||
provider |
Optional. Required for updates. The identifier for the provider from which the order data was received in lower snake cased form. For example, shopify or my_store. Used to identify the order for updates. |
||||||||||||||||||||||||||||||
tax |
Optional. The tax on the order in cents (e.g. if the order was $1.00, set the tax to 100). |
||||||||||||||||||||||||||||||
fees |
Optional. The fees on the order in cents (e.g. if the order was $1.00, set the fees to 100). |
||||||||||||||||||||||||||||||
discount |
Optional. The discount on the order in cents (e.g. if the order was $5.00, set the discount to 500). |
||||||||||||||||||||||||||||||
permalink |
Optional. A URL for the human-readable interface to view the order details. | ||||||||||||||||||||||||||||||
currency_code |
Optional. The alphabetic ISO 4217 code for the currency of the purchase. | ||||||||||||||||||||||||||||||
upstream_id |
Optional. Required for updates. A unique, internal id for the order (generally the primary key generated by the order management system). Used to identify the order for updates. | ||||||||||||||||||||||||||||||
identifier |
Optional. A unique, external identifier for the order. This will be displayed in the Drip UI and should correspond to what a customer sees on invoices, email confirmations, etc. | ||||||||||||||||||||||||||||||
properties |
Optional. An Object containing properties about the order. E.g. { "size": "large" } |
||||||||||||||||||||||||||||||
occurred_at |
Optional. The String time at which the order occurred in ISO-8601 format. Defaults to the current time. | ||||||||||||||||||||||||||||||
closed_at |
Optional. The String time at which the order closed in ISO-8601 format. | ||||||||||||||||||||||||||||||
cancelled_at |
Optional. The String time at which the order was cancelled in ISO-8601 format. For an order that was not cancelled, this will be nil. |
||||||||||||||||||||||||||||||
financial_state |
Optional. The financial status of the order. One of the following values: pending, authorized, partially_paid, paid, partially_refunded, refunded, voided. |
||||||||||||||||||||||||||||||
fulfillment_state |
Optional. The fulfillment status of the order. One of the following values: not_fulfilled, partially_fulfilled, fulfilled. |
||||||||||||||||||||||||||||||
billing_address |
Optional. An object containing billing address information.
|
||||||||||||||||||||||||||||||
shipping_address |
Optional. An object containing shipping address information.
|
||||||||||||||||||||||||||||||
items |
Optional. An Array of objects containing information about specific order items.
|
To create or update a refund for an order:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/refunds" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"refunds": [{
"provider": "shopify",
"order_upstream_id": "abcdef",
"upstream_id": "tuvwx",
"amount": 2000,
"note": "Incorrect size",
"processed_at": "2013-06-22T10:41:11Z"
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
refund = {
provider: "shopify",
order_upstream_id: "abcdef",
upstream_id: "tuvwx",
amount: 2000,
note: "Incorrect size",
processed_at: "2013-06-22T10:41:11Z"
}
response = client.create_or_update_refund(refund)
if response.success?
# ...
end
Responds with a
202 Acceptedand an empty JSON response:
{}
When a refund is created, the subscriber's lifetime_value attribute
will be automatically decremented by the total amount of the refund.
To update an existing refund, include the upstream_id for that refund in the payload.
POST /v2/:account_id/refunds
| Property | Description |
|---|---|
provider |
Required. The provider for the Order being refunded. |
order_upstream_id |
Required. The upstream_id for the Order being refunded. |
amount |
Required. The amount of the refund. |
upstream_id |
Optional. Required for updates. The unique id of refund in the order management system. Used to identify the refund for updates. |
note |
Optional. A note about the refund. |
processed_at |
Optional. The String time at which the refund was processed in ISO-8601 format. |
Drip automatically records a number of events in the life cycle of an order. You can find
triggers for these events by selecting Drip Order API from the provider dropdown in your Drip automation.
| Event | Description |
|---|---|
Placed an order |
This event is fired when an order is created. |
Updated an order |
This event is fired when an existing order is updated. |
Paid an order |
This event is fired when the financial_state on an order is set to paid. |
Fulfilled an order |
This event is fired when the fulfillment_state on an order is set to fulfilled. |
Canceled an order |
This event is fired when the cancelled_at time on an order is set. |
Refunded an order |
This event is fired when a refund is created for an order. |
Segments are saved groups of people, built in the Drip app from a set of rules. This API is read-only: segments are created and edited in the app, and read here so you can find one by name and point a Single-Email Campaign at it.
The id returned here is the value the Single-Email Campaigns API takes as segment_id when
setting an audience.
Only active, permanent segments are returned. A deleted, expired, or temporary segment is treated as though it does not exist.
To list all segments:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments")
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments",
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
Responds with a
200 OK:
{
"links": { ... },
"meta": {
"page": 1,
"count": 2,
"total_pages": 1,
"total_count": 2
},
"segments": [{
"id": "4815162",
"href": "https://api.getdrip.com/v2/9999999/segments/4815162",
"name": "VIP customers",
"created_at": "2026-01-14T09:22:41Z",
"updated_at": "2026-08-02T17:05:03Z",
"links": { "account": "9999999" }
}]
}
Results come back in a stable but unspecified order. Ordering is not part of the contract yet.
GET /v2/:account_id/segments
| Argument | Description |
|---|---|
| page | Optional. The page number. Defaults to 1. |
| per_page | Optional. The number of segments per page. Defaults to 100, maximum 100. |
| q | Optional. Case-insensitive substring match on the segment's name. |
To fetch a segment:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments/SEGMENT_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments/SEGMENT_ID")
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/segments/SEGMENT_ID",
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
Responds with a
200 OK:
{
"links": { ... },
"segments": [{
"id": "4815162",
"href": "https://api.getdrip.com/v2/9999999/segments/4815162",
"name": "VIP customers",
"created_at": "2026-01-14T09:22:41Z",
"updated_at": "2026-08-02T17:05:03Z",
"derived": {
"summary": "People who are tagged with \"vip\" and have ordered in the last 90 days"
},
"links": { "account": "9999999" }
}]
}
derived.summary is a plain-English description of the segment's rules, generated when you
read it. It is display text: do not parse it, and expect the wording to change. It is returned
here only, not in the list, because generating it walks the segment's rules.
GET /v2/:account_id/segments/:segment_id
None.
Subscribers are represented as follows:
{
"id": "z1togz2hcjrkpp5treip",
"status": "active",
"email": "[email protected]",
"first_name": "John",
"last_name": "Doe",
"address1": "123 Main St.",
"address2": "Suite 200",
"city": "Los Angeles",
"state": "CA",
"zip": "90210",
"country": "US",
"phone": "555-555-5555",
"sms_number": "+16125551212",
"sms_consent": true,
"eu_consent": "granted",
"time_zone": "America/Los_Angeles",
"utc_offset": -440,
"visitor_uuid": "sa8f7sdf78sdsdahf788d7asf8sd",
"deliverable": true,
"custom_fields": {
"shirt_size": "Medium"
},
"tags": ["Customer", "SEO"],
"ip_address": "111.111.111.11",
"user_agent": "Chrome/36.0.1985.143",
"original_referrer": "http://www.getdrip.com",
"landing_url": "https://www.getdrip.com/docs/rest-api",
"prospect": true,
"lead_score": 72,
"lifetime_value": 10000,
"created_at": "2013-06-21T10:31:58Z",
"href": "https://api.getdrip.com/v2/9999999/subscribers/12345",
"user_id": "12345",
"base_lead_score": 30,
"links": {
"account": "9999999"
}
}
All responses containing subscriber data also include the following top-level link data:
{
"links": {
"subscribers.account": "https://api.getdrip.com/v2/accounts/{subscribers.account}"
}
}
Note: All subscriber API endpoints only work with your active people. Attempting to modify or delete an inactive person will result in an error.
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each subscriber record. |
status |
The subscriber's status whether active or unsubscribed. |
initial_status |
The subscriber's known status whether active or unsubscribed. To be used instead of status if subscriber's status is unchanged. |
email |
The subscriber's email address. |
first_name |
The subscriber's first name. |
last_name |
The subscriber's last name. |
address1 |
The subscriber's mailing address. |
address2 |
An additional field for the subscriber's mailing address. |
city |
The city, town, or village in which the subscriber resides. |
state |
The region in which the subscriber resides. Typically a province, a state, or a prefecture. |
zip |
The postal code in which the subscriber resides, also known as zip, postcode, Eircode, etc. |
country |
The country in which the subscriber resides. |
phone |
The subscriber's primary phone number. |
sms_number |
Optional. String. The subscriber's mobile phone number in E.164 formatting. E.g. "+16125551212". Only US-based numbers are supported at this time. |
sms_consent |
Optional. Boolean. true if the person has granted consent to receive marketing and other communication via SMS; false otherwise. Default: false. If you’re unsure whether or not you have gained legal SMS consent, check out our TCPA requirements article. |
eu_consent |
A string describing whether the subscriber GDPR consent is granted, denied, or unknown. |
time_zone |
The subscriber's time zone (in Olson format). |
utc_offset |
The UTC offset in minutes relative to UTC/GMT. |
visitor_uuid |
A read-only Drip generated unique id used to identify each subscriber's visitor record if available. |
deliverable |
Boolean. true if Drip can send email to this subscriber; false if the address has been marked undeliverable (for example, after a hard bounce). Independent of status: an active subscriber may be undeliverable. Read-only; ignored if supplied on create or update. |
custom_fields |
An Object containing custom field data. E.g. { "shirt_size": "Medium" }. |
tags |
An Array containing one or more tags. E.g. ["Customer", "SEO"]. |
ip_address |
The subscriber's IP address if available. |
user_agent |
The subscriber's browser User Agent if available. |
original_referrer |
The initial referral URL when the subscriber first visited your site if available. |
landing_url |
The first page visited by the subscriber if available. |
prospect |
Returns true if the subscriber is marked as a lead. |
lead_score |
The subscriber's lead score. |
lifetime_value |
The subscriber's lifetime value in cents. |
created_at |
A timestamp representing when the subscriber record was first created. |
href |
The url designated for retrieving the subscriber record via the REST API. |
user_id |
A unique identifier for the user in your database, such as a primary key. |
base_lead_score |
An Integer specifying the starting value for lead score calculation for this subscriber. Defaults to 30. |
links |
An object containing the REST API URL for the account. |
To create or update a subscriber:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-H 'Content-Type: application/json' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"subscribers": [{
"email": "[email protected]",
"time_zone": "America/Los_Angeles",
"custom_fields": {
"shirt_size": "Medium"
}
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
options = {
time_zone: "America/Los_Angeles",
custom_fields: {
shirt_size: "Medium"
}
}
response = client.create_or_update_subscriber(subscriber_email, options)
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const payload = {
email: "[email protected]",
time_zone: "America/Los_Angeles",
custom_fields: {
shirt_size: "Medium"
}
};
client.createUpdateSubscriber(payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
POST /v2/:account_id/subscribers
If you need to create or update a collection of subscribers at once, use our batch API instead.
Note: Concurrently updating the same subscriber via multiple API calls is not supported and will fail with a rate limit error and the message "Too many concurrent requests for the same subscriber". You should retry the call after a short wait period to let the other requests to the same subscriber complete. Triggering this rate limit does not mean you've consumed your overall API rate limited capacity.
| Key | Description |
|---|---|
email |
Optional. The subscriber's email address. Either email or id or visitor_uuid must be included. |
id |
Optional. The subscriber's Drip id. Either email or id or visitor_uuid must be included. |
visitor_uuid |
Optional. The uuid for a subscriber's visitor record. Either email or id or visitor_uuid must be included. |
new_email |
Optional. A new email address for the subscriber. If provided and a subscriber with the email above does not exist, this address will be used to create a new subscriber. |
first_name |
Optional. The subscriber's first name. |
last_name |
Optional. The subscriber's last name. |
address1 |
Optional. The subscriber's mailing address. |
address2 |
Optional. An additional field for the subscriber's mailing address. |
city |
Optional. The city, town, or village in which the subscriber resides. |
state |
Optional. The region in which the subscriber resides. Typically a province, a state, or a prefecture. |
zip |
Optional. The postal code in which the subscriber resides, also known as zip, postcode, Eircode, etc. |
country |
Optional. The country in which the subscriber resides. |
phone |
Optional. The subscriber's primary phone number. |
sms_number |
Optional. String. The subscriber's mobile phone number in E.164 formatting. E.g. "+16125551212". Only US-based numbers are supported at this time. |
sms_consent |
Optional. Boolean. true if the person has granted consent to receive marketing and other communication via SMS; false otherwise. Default: false. If you’re unsure whether or not you have gained legal SMS consent, check out our TCPA requirements article. |
user_id |
Optional. A unique identifier for the user in your database, such as a primary key. |
time_zone |
Optional. The subscriber's time zone (in Olson format). Defaults to Etc/UTC |
lifetime_value |
Optional. The lifetime value of the subscriber (in cents). |
ip_address |
Optional. The subscriber's ip address E.g. "111.111.111.11" |
custom_fields |
Optional. An Object containing custom field data. E.g. { "shirt_size": "Medium" }. |
tags |
Optional. An Array containing one or more tags. E.g. ["Customer", "SEO"]. |
remove_tags |
Optional. An Array containing one or more tags to be removed from the subscriber. E.g. ["Customer", "SEO"]. |
prospect |
Optional. A Boolean specifiying whether we should attach a lead score to the subscriber (when lead scoring is enabled). Defaults to true.
Note: This flag used to be called potential_lead, which we will continue to accept for backwards compatibility. |
base_lead_score |
Optional. An Integer specifying the starting value for lead score calculation for this subscriber. Defaults to 30. |
eu_consent |
Optional. A String specifying whether the subscriber granted or denied GDPR consent. |
eu_consent_message |
Optional. A String containing the message the subscriber granted or denied their consent to. |
status |
Optional. A String specifying the subscriber's status: either active or unsubscribed. |
initial_status |
Optional. A String specifying the subscriber's known status: either active or unsubscribed. To be used if subscriber's status is unchanged. |
To list subscribers:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.subscribers
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { status: "active", subscribed_before: "2017-01-01T00:00:00Z" };
client.listSubscribers(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array subscriber objects.
# The meta property contains pagination information.
{
"links": { ... },
"meta": {
"page": 1,
"count": 5,
"total_pages": 1,
"total_count": 5
},
"subscribers": [{ ... }]
}
GET /v2/:account_id/subscribers
| Key | Description |
|---|---|
status |
Optional. Filter by one of the following statuses: all, active, unsubscribed, active_or_unsubscribed or undeliverable. Defaults to active. |
tags |
Optional. A comma separated list of tags. When included, returns only subscribers who have at least one of the listed tags. |
subscribed_before |
Optional. A ISO-8601 datetime. When included, returns only subscribers who were created before the date. Eg. "2017-01-01T00:00:00Z" |
subscribed_after |
Optional. A ISO-8601 datetime. When included, returns only subscribers who were created after the date. Eg. "2016-01-01T00:00:00Z" |
page |
Optional. The page number. Defaults to 1. |
per_page |
Optional. The number of records to be returned on each page. Defaults to 100. Maximum 1000. |
To fetch a subscriber:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL_OR_VISITOR_UUID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
response = client.subscriber(subscriber_email)
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const idOrEmail = "[email protected]";
client.fetchSubscriber(idOrEmail)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
GET /v2/:account_id/subscribers/:id_or_email_or_visitor_uuid
None.
To remove a subscriber from all Email Series s:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL/remove" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
##
# To remove a subscriber from all Email Series Campaigns:
##
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
response = client.unsubscribe(subscriber_email)
if response.success?
puts response.body["subscribers"]
end
##
# To remove a subscriber from a specific campaign:
##
subscriber_email = "[email protected]"
options = {
campaign_id: 9999999
}
response = client.unsubscribe(subscriber_email, options)
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const idOrEmail = "[email protected]";
const campaignId = 9998888;
client.unsubscribeFromCampaign(idOrEmail, campaignId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
To remove a subscriber from a specific campaign:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL/remove?campaign_id=CAMPAIGN_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
POST /v2/:account_id/subscribers/:id_or_email/remove
This endpoint was previously labeled unsubscribe.
| Key | Description |
|---|---|
campaign_id |
Optional. The Email Series Campaign from which to remove the subscriber. Defaults to all. |
To unsubscribe a subscriber from all mailings:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL/unsubscribe_all" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
response = client.unsubscribe_from_all(subscriber_email)
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const idOrEmail = "[email protected]";
client.unsubscribeFromAllMailings(idOrEmail)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
POST /v2/:account_id/subscribers/:id_or_email/unsubscribe_all
None.
To delete a subscriber:
curl -X DELETE "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
response = client.delete_subscriber(subscriber_email)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const idOrEmail = "[email protected]";
client.deleteSubscriber(idOrEmail)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with
204 No Contentif successful.
DELETE /v2/:account_id/subscribers/:id_or_email
None.
To list all tags:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/tags" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.tags
if response.success?
puts response.body["tags"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
client.listAllTags()
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"tags": [
"Customer",
"SEO"
]
}
Properties
| Property | Description |
|---|---|
email |
The subscriber's email address. |
tag |
The String tag to apply. E.g. "Customer". |
GET /v2/:account_id/tags
None.
To apply a tag to a specific subscriber:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/tags" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"tags": [{
"email": "[email protected]",
"tag": "Customer"
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
tag = "Customer"
response = client.apply_tag(subscriber_email, tag)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const payload = {
tags: [{
email: "[email protected]",
tag: "Customer"
}]
};
client.tagSubscriber(payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
201 Createdand an empty JSON response:
{}
POST /v2/:account_id/tags
| Key | Description |
|---|---|
email |
The subscriber's email address. |
tag |
The String tag to apply. E.g. "Customer" |
To apply a tag to a specific subscriber:
curl -X DELETE "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/subscribers/ID_OR_EMAIL/tags/TAG" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
subscriber_email = "[email protected]"
tag = "Customer"
response = client.remove_tag(subscriber_email, tag)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const email = "[email protected]";
const tag = "Customer";
client.removeSubscriberTag(email, tag)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
DELETE /:account_id/subscribers/:email/tags/:tag
None.
Users are represented as follows:
{
"email": "[email protected]",
"name": "John Doe",
"time_zone": "America/Los_Angeles"
}
To fetch the current user:
curl "https://api.getdrip.com/v2/user" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
client.fetchUser()
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"users":[{
"email": "[email protected]",
"name": "John Doe",
"time_zone": "America/Los_Angeles"
}]
}
GET /v2/user
None.
Workflows are represented as follows:
{
"id": "123456",
"href": "https://api.getdrip.com/v2/9999999/workflows/123456",
"name": "Main Funnel",
"status": "active",
"created_at": "2016-07-01T10:00:00Z",
"links": {
"account": "9999999"
}
}
All responses containing workflow data also include the following top-level link data:
{
"links": {
"workflows.account": "https://api.getdrip.com/v2/accounts/{workflows.account}"
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each workflow record. |
href |
The url designated for retrieving the workflow record via the REST API. |
name |
The name assigned to the workflow. |
status |
The workflow's status whether draft, active, or paused. |
created_at |
A timestamp representing when the workflow record was first created. |
links |
An object containing the REST API URL for the account. |
To list all workflows:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.workflows
if response.success?
puts response.body["workflows"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const options = { status: "active" };
client.listAllWorkflows(options)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The workflows property is an array of workflow objects.
{
"links": { ... },
"meta": {
"page": 1,
"sort": "sort_order",
"direction": "desc",
"count": 5,
"total_pages": 1,
"total_count": 5,
"status": "all"
},
"workflows": [ ... ]
}
GET /v2/:account_id/workflows
| Key | Description |
|---|---|
status |
Optional. Filter by one of the following statuses: draft, active, or paused. Defaults to all. |
sort |
Optional. Sort results by one of these fields: created_at or name. Defaults to created_at. |
direction |
Optional. Filter sort direction with: asc or desc. Defaults to asc. |
To fetch a workflow:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
response = client.workflow(workflow_id)
if response.success?
puts response.body["workflows"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
client.fetchWorkflow(workflowId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The workflows property is an array of one workflow object.
{
"links": { ... },
"workflows": [{ ... }]
}
GET /v2/:account_id/workflows/:workflow_id
None.
The details endpoint returns the structure of a single workflow: its entry triggers, its ordered steps, and everything nested beneath its decisions, goals, forks, and split tests. It is read-only.
Workflows in every visible status are returned, including drafts.
To fetch a workflow's details:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/details" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/details")
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/details",
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
The response looks like this (abridged):
# The workflows property is an array of one workflow object.
{
"links": {
"workflows.account": "https://api.getdrip.com/v2/accounts/{workflows.account}"
},
"workflows": [
{
"id": "12345",
"href": "https://api.getdrip.com/v2/9999999/workflows/12345",
"name": "Welcome series",
"description": "Onboarding for new trial signups",
"status": "active",
"created_at": "2026-01-04T17:22:03Z",
"updated_at": "2026-07-28T14:02:11Z",
"node_count": 9,
"details": {
"id": "root_1",
"type": "path",
"triggers": [
{
"id": "0a1b2c3d",
"type": "trigger",
"trigger_type": "submitted_form",
"provider_id": "drip",
"entry_point": true,
"status": "active",
"properties": { "form_id": "185216953", "source": "drip" },
"incomplete": false,
"actions_required": [],
"derived": {
"type_name": "Submitted a form or Onsite campaign.",
"summary": "Submitted the \"Trial signup\" form",
"type_description": "This event is fired when the person submits a form or Onsite campaign.",
"segment_description": "people who are tagged with \"trial\""
}
}
],
"steps": [
{
"id": "4e5f6a7b",
"type": "action",
"action_type": "send_email",
"provider_id": "drip",
"status": "active",
"properties": { "automation_email_id": "957660884" },
"incomplete": false,
"actions_required": [],
"email": { "id": "957660884", "subject": "Welcome, {{ subscriber.first_name }}!" },
"derived": {
"type_name": "Send an email to a person",
"summary": "Send \"Welcome!\"",
"email_title": "Welcome!"
}
},
{
"id": "8c9d0e1f",
"type": "delay",
"delay_type": "wait_for_relative_date",
"properties": {
"duration": 2,
"units": "days",
"days_of_the_week_mask": "0111110",
"minutes_from_midnight": 540,
"time_zone": "owner"
},
"incomplete": false,
"derived": { "summary": "Wait 2 days, then resume on the next weekday at 9:00 am" }
},
{
"id": "2a3b4c5d",
"type": "decision",
"true_path": {
"id": "6e7f8a9b",
"type": "path",
"triggers": [],
"steps": [
{
"id": "0c1d2e3f",
"type": "action",
"action_type": "apply_tag",
"provider_id": "drip",
"status": "active",
"properties": { "tag": "vip" },
"incomplete": false,
"actions_required": [],
"derived": { "type_name": "Apply a tag", "summary": "Apply the \"vip\" tag" }
}
]
},
"false_path": { "id": "4a5b6c7d", "type": "path", "triggers": [], "steps": [] },
"incomplete": false,
"actions_required": [],
"derived": { "summary": "Person is tagged \"engaged\"?" }
},
{
"id": "8e9f0a1b",
"type": "interrupt",
"triggers": [ { "id": "2c3d4e5f", "type": "trigger", "...": "..." } ],
"derived": { "summary": "Waiting for goals" }
},
{
"id": "6a7b8c9d",
"type": "split_test",
"properties": { "workflow_split_test_id": "9z8y7x6w" },
"paths": [
{ "id": "0e1f2a3b", "type": "split_test_path", "triggers": [], "steps": [] },
{ "id": "4c5d6e7f", "type": "split_test_path", "triggers": [], "steps": [] }
],
"derived": { "summary": "Began a split test" }
},
{
"id": "3c4d5e6f",
"type": "exit",
"derived": { "summary": "Exit the workflow" }
}
]
},
"links": { "account": "9999999" }
}
]
}
Properties
In addition to the properties every workflow record carries, the details payload includes:
| Property | Description |
|---|---|
description |
The description assigned to the workflow. May be empty. |
node_count |
The number of nodes in the workflow, counting containers. |
details |
The root path node, holding the workflow's entry triggers and its ordered steps. |
Every node in the tree carries an id and a type, so one key tells you how to read the rest of
the node. Container nodes hold their children under triggers, steps, paths, true_path, or
false_path, depending on their type. Branches are objects rather than bare arrays, so an empty
branch still has an id.
Node type |
Fields | derived keys |
|---|---|---|
path, split_test_path |
id, type, triggers, steps |
None |
trigger |
id, type, trigger_type, provider_id, entry_point, status, properties, incomplete, actions_required |
type_name, summary, type_description, segment_description |
action |
id, type, action_type, provider_id, status, properties, incomplete, actions_required, email (send_email only) |
type_name, summary, email_title |
delay |
id, type, delay_type, properties, incomplete |
summary |
decision |
id, type, true_path, false_path, incomplete, actions_required |
summary |
fork |
id, type, paths |
None |
interrupt |
id, type, triggers |
summary |
split_test |
id, type, properties, paths |
summary |
exit |
id, type |
summary |
An interrupt node is a goal: the triggers beneath it are goal triggers rather than entry
triggers, and they carry entry_point: false. A node's status is active or draft.
New fields may be added to any node at any time. Ignore fields you do not recognize.
properties is the node's configuration as stored, and it is present on every trigger, action,
delay, and split_test node. Trigger properties are the same object
List all workflow triggers returns for the same trigger.
An empty properties object does not mean the node is unconfigured; incomplete answers that.
Several action types, such as activate_person and subscribe, store no fields even when fully
configured.
incomplete is true whenever a node is not fully configured, whether because no type has been
chosen, because the node is blocked, or because it carries any actions_required. A decision whose
criteria have not been set is incomplete in the same way. actions_required lists what is missing,
as an array of { "code": ..., "message": ... } objects, in the same shape
List all workflow triggers returns.
Some configuration is never published. A drip/http_post action responds with an empty
properties object and no derived.summary, because its endpoint URL and request headers are
credentials; only derived.type_name survives. Raw segmentation criteria, provider credentials,
and editor layout data are not published on any node.
A send_email action whose email exists also carries an email object with the email's id and
subject. email.id is the email's public id: pass it to
Fetch a workflow email for the preheader and full HTML and text bodies.
Use email.id rather than properties.automation_email_id, which is published as stored and may
hold a legacy id that Fetch a workflow email does not accept. The subject is as stored, with any
Liquid tags verbatim, and is null or empty on a draft whose subject has not been written.
Human-readable text lives in a derived object, separate from properties. Everything in
derived is generated when the response is built and is not part of the workflow's stored
definition.
| Key | Description |
|---|---|
summary |
What this particular node does. E.g. Send "Welcome!". |
type_name |
The display name of the node's type. Triggers and actions only. |
type_description |
What the node's type means in general. Triggers only. |
segment_description |
The humanized entry criteria for the trigger. Triggers only. |
email_title |
The title of the email the node sends. send_email actions only. |
Any key is omitted when it has no value, and a node with no text at all carries no derived
object. Because these strings are built from the records the node refers to, a derived value can
change when some other record is renamed, with no edit to the workflow itself. Use properties and
the type fields for anything you parse.
A node's id holds the same value the Metrics endpoint returns as
workflow_placement.node_id, so joining details node id to workflow_placement.node_id lines
the structure up with per-email performance. The two endpoints spell the field differently; the
values are identical.
A node id is stable for the life of the workflow record. Copying a workflow regenerates every node id, so ids do not carry across a copy.
If the response has not changed, responds with a
304 Not Modifiedand an empty body:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/details" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-H 'If-None-Match: "a1b2c3d4e5f6"' \
-u YOUR_API_KEY:
Responses carry an ETag covering every field of the response. Send it back as If-None-Match to
get a 304 Not Modified when none of those fields has changed.
The ETag describes this view of the workflow rather than the workflow's stored definition, so it
is a cache validator only. Two workflows that differ solely in fields this endpoint withholds share
an ETag, and a change confined to a withheld field does not invalidate one.
If the rate limit is exceeded, responds with a
429 Too Many Requestsand aRetry-Afterheader of3600:
{
"errors": [{
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded: at most 2000 requests per account per hour."
}]
}
Fetching a workflow's details has its own rate limit of 2,000 requests per account per hour, in
addition to the general rate limit. The limit counts requests in fixed one-hour
windows, and Retry-After is always the full hour (3600 seconds), not the time left in the
current window. Fetch a workflow email has only the general rate limit.
GET /v2/:account_id/workflows/:workflow_id/details
None.
Returns one email a workflow sends: its subject, preheader, and full HTML and text bodies, along with its name, builder type, and sender. It is read-only.
The email is identified by its public id, the email.id a send_email action carries in
Fetch a workflow's details. An email is returned only through a
workflow with a send_email step that names it.
To fetch a workflow email:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/emails/WORKFLOW_EMAIL_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require "net/http"
require "json"
uri = URI("https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/emails/WORKFLOW_EMAIL_ID")
request = Net::HTTP::Get.new(uri)
request.basic_auth("YOUR_API_KEY", "")
request["User-Agent"] = "Your App Name (www.yourapp.com)"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
const response = await fetch(
"https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/emails/WORKFLOW_EMAIL_ID",
{
headers: {
"User-Agent": "Your App Name (www.yourapp.com)",
"Authorization": "Basic " + Buffer.from("YOUR_API_KEY:").toString("base64")
}
}
);
const body = await response.json();
The response looks like this:
# The emails property is an array of one email object.
{
"links": {
"emails.account": "https://api.getdrip.com/v2/accounts/{emails.account}",
"emails.workflow": "https://api.getdrip.com/v2/{emails.account}/workflows/{emails.workflow}"
},
"emails": [
{
"id": "957660884",
"href": "https://api.getdrip.com/v2/9999999/workflows/12345/emails/957660884",
"node_id": "4e5f6a7b",
"email_id": 4455661,
"name": "Welcome!",
"builder_type": "text",
"from_name": "John Doe",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"subject": "Welcome, {{ subscriber.first_name }}!",
"preheader": "Here's how to get started",
"html_body": "<p>Hi {{ subscriber.first_name }},</p>{% if subscriber.tags contains 'vip' %}<p>Thanks for being a VIP.</p>{% endif %}",
"text_body": "Hi {{ subscriber.first_name }},",
"created_at": "2026-01-04T17:22:03Z",
"updated_at": "2026-07-28T14:02:11Z",
"links": {
"account": "9999999",
"workflow": "12345"
}
}
]
}
Properties
| Property | Description |
|---|---|
id |
The email's public id. The same value as email.id on the send_email action that sends it. |
href |
The url designated for retrieving the email via the REST API. |
node_id |
The id of the send_email node in Fetch a workflow's details that sends the email. |
email_id |
The value the Metrics endpoint returns as email_id for this email, so it joins the email to its performance. Not the id this endpoint takes; use id. |
name |
The name assigned to the email. |
builder_type |
The editor the email was built with, whether text or visual (the Visual Email Builder). |
from_name |
The From name set on the email. May be null. |
from_email |
The address the email is sent from: its own From address, or the account's default From address if it has none. May be null. |
reply_to |
The Reply-To address set on the email. May be null, in which case replies go to the From address. |
subject |
The subject line. |
preheader |
The preheader text. |
html_body |
The HTML body. For a Visual Email Builder email, this is the full generated HTML; for other emails, it is the body only, without the account's email template or footer. |
text_body |
The plain text body. |
created_at |
A timestamp representing when the email record was first created. |
updated_at |
A timestamp representing when the email record was last updated. |
links |
An object containing the ids of the account and the workflow. |
Content is returned as stored, as it is for Single-Email Campaigns: Liquid tags such as
{{ subscriber.first_name }} and {% if %} blocks appear verbatim and are not rendered for any
subscriber. subject, preheader, and the bodies may be null or empty when not yet written, as on
a draft email.
New fields may be added at any time. Ignore fields you do not recognize.
If no
send_emailstep in the workflow names the email, responds with a404 Not Found:
{
"errors": [{
"code": "not_found_error",
"message": "This workflow has no send-email step for that email id. Use the email's public id: the `email.id` of a send_email action in GET /v2/:account_id/workflows/:workflow_id/details."
}]
}
The same 404 is returned for the id of a Single-Email Campaign, of an Email Series Campaign email,
or of an email sent by a rule or a form, or an old id from properties.automation_email_id: only
the public id in email.id is accepted. A workflow that does not exist or is archived responds with
the standard 404 Not Found described in Errors.
GET /v2/:account_id/workflows/:workflow_id/emails/:workflow_email_id
None.
To activate a workflow:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/activate" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
response = client.activate_workflow(workflow_id)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
client.activateWorkflow(workflowId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
POST /v2/:account_id/workflows/:workflow_id/activate
None.
To pause a workflow:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/pause" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
response = client.pause_workflow(workflow_id)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
client.pauseWorkflow(workflowId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
POST /v2/:account_id/workflows/:workflow_id/pause
None.
To start a someone on a workflow:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/subscribers" \
-H "Content-Type: application/json" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"subscribers": [{
"email": "[email protected]",
"time_zone": "America/Los_Angeles",
"custom_fields": {
"shirt_size": "Medium"
}
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
options = {
email: "[email protected]",
time_zone: "America/Los_Angeles",
custom_fields: {
shirt_size: "Medium"
}
}
response = client.start_subscriber_workflow(workflow_id, options)
if response.success?
puts response.body["subscribers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
const payload = {
subscribers: [{
email: "[email protected]",
time_zone: "America/Los_Angeles",
custom_fields: {
shirt_size: "Medium"
}
}]
}
client.startOnWorkflow(workflowId, payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The subscribers property is an array of one object.
{
"links": { ... },
"subscribers": [{ ... }]
}
If the workflow is not active, the subscriber will not be added to the workflow.
POST /v2/:account_id/workflows/:workflow_id/subscribers
| Key | Description |
|---|---|
email |
Optional. The subscriber's email address. Either email or id must be included. |
id |
Optional. The subscriber's Drip id. Either email or id must be included. |
first_name |
Optional. The subscriber's first name. |
last_name |
Optional. The subscriber's last name. |
address1 |
Optional. The subscriber's mailing address. |
address2 |
Optional. An additional field for the subscriber's mailing address. |
city |
Optional. The city, town, or village in which the subscriber resides. |
state |
Optional. The region in which the subscriber resides. Typically a province, a state, or a prefecture. |
zip |
Optional. The postal code in which the subscriber resides, also known as zip, postcode, Eircode, etc. |
country |
Optional. The country in which the subscriber resides. |
phone |
Optional. The subscriber's primary phone number. |
user_id |
Optional. A unique identifier for the user in your database, such as a primary key. |
time_zone |
Optional. The subscriber's time zone (in Olson format). Defaults to Etc/UTC |
custom_fields |
Optional. An Object containing custom field data. E.g. { "shirt_size": "Medium" }. |
tags |
Optional. An Array containing one or more tags. E.g. ["Customer", "SEO"]. |
prospect |
Optional. A Boolean specifiying whether we should attach a lead score to the subscriber (when lead scoring is enabled). Defaults to true.
Note: This flag used to be called potential_lead, which we will continue to accept for backwards compatibility. |
eu_consent |
Optional. A String specifying whether the subscriber granted or denied GDPR consent. |
eu_consent_message |
Optional. A String containing the message the subscriber granted or denied their consent to. |
To remove someone from a workflow:
curl -X DELETE "https://v2/api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/subscribers/ID_OR_EMAIL" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
subscriber_email = "[email protected]"
response = client.remove_subscriber_workflow(workflow_id, subscriber_email)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
const idOrEmail = "[email protected]"
client.removeFromWorkflow(workflowId, idOrEmail)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
If the subscriber is not already on the workflow, nothing will happen.
DELETE /v2/:account_id/workflows/:workflow_id/subscribers/:id_or_email
None.
To list all triggers on a workflow:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/triggers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
response = client.workflow_triggers(workflow_id)
if response.success?
puts response.body["triggers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
client.listTriggers(workflowId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"triggers": [
{
"id": "f7gysdf7gyd7",
"type": "trigger",
"trigger_type": "submitted_landing_page",
"provider": "leadpages",
"properties": {
"landing_page": "My Landing Page"
},
"actions_required": [
{
"code": "configure_provider",
"message": "Configure your LeadPages connection"
}
]
}
]
}
GET /v2/:account_id/workflows/:workflow_id/triggers
None.
To create a workflow trigger:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/triggers" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"triggers": [
{
"provider": "leadpages",
"trigger_type": "submitted_landing_page",
"properties": {
"landing_page": "My Landing Page"
}
}
]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
options = {
provider: "leadpages",
trigger_type: "submitted_landing_page",
properties: {
landing_page: "My Landing Page"
}
}
response = client.create_workflow_trigger(workflow_id, options)
if response.success?
puts response.body["triggers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
const payload = {
provider: "leadpages",
trigger_type: "submitted_landing_page",
properties: {
landing_page: "My Landing Page"
}
}
client.createTrigger(workflowId, payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"triggers": [
{
"id": "f7gysdf7gyd7",
"type": "trigger",
"provider": "leadpages",
"trigger_type": "submitted_landing_page",
"properties": {
"landing_page": "My Landing Page"
},
"actions_required": [
{
"code": "configure_provider",
"message": "Configure your LeadPages connection"
}
]
}
]
}
POST /v2/:account_id/workflows/:workflow_id/triggers
| Key | Description |
|---|---|
provider |
Required. A String indicating a provider. |
trigger_type |
Required. A String indicating the automation trigger type. |
properties |
Optional. An Object containing properties for the given trigger. |
To update a workflow trigger:
curl -X PUT "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/workflows/WORKFLOW_ID/triggers/TRIGGER_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"triggers": [
{
"provider": "leadpages",
"trigger_type": "submitted_landing_page",
"properties": {
"landing_page": "My Landing Page"
}
}
]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
workflow_id = 9999999
options = {
provider: "leadpages",
trigger_type: "submitted_landing_page",
properties: {
landing_page: "My Landing Page"
}
}
response = client.update_workflow_trigger(workflow_id, options)
if response.success?
puts response.body["triggers"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const workflowId = 222333;
const triggerId = "abc123";
const payload = {
provider: "leadpages",
trigger_type: "submitted_landing_page",
properties: {
landing_page: "My Landing Page"
}
}
client.updateTrigger(workflowId, triggerId, payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
{
"triggers": [
{
"id": "f7gysdf7gyd7",
"type": "trigger",
"provider": "leadpages",
"trigger_type": "submitted_landing_page",
"properties": {
"landing_page": "My Landing Page"
},
"actions_required": [
{
"code": "configure_provider",
"message": "Configure your LeadPages connection"
}
]
}
]
}
PUT /v2/:account_id/workflows/:workflow_id/triggers/:trigger_id
| Key | Description |
|---|---|
provider |
Required. A String indicating a provider. |
trigger_type |
Required. A String indicating the automation trigger type. |
properties |
Optional. An Object containing properties for the given trigger. |
Webhooks are represented as follows:
{
"id": "77777",
"href": "https://api.getdrip.com/v2/9999999/webhooks/77777",
"post_url": "http://www.mysite.com/my-webhook-endpoint",
"version": "1",
"include_received_email": false,
"events": [
"subscriber.created",
"subscriber.subscribed_to_campaign"
],
"created_at": "2013-06-21T10:31:58Z",
"links": {
"account": "9999999",
}
}
All responses containing webhook data also include the following top-level link data:
{
"links": {
"webhooks.account": "https://api.getdrip.com/v2/accounts/{webhooks.account}"
}
}
Properties
| Property | Description |
|---|---|
id |
A read-only Drip generated unique id used to identify each webhook record. |
href |
The url designated for retrieving the webhook record via the REST API. |
post_url |
The url that the webhook will post to. |
version |
The webhook version. The current stable version is 2. |
include_received_email |
Returns true if a notification is sent whenever a subscriber receives an email. |
created_at |
A timestamp representing when the webhook record was first created. |
events |
An array specifying which events are enabled for webhook notifications. |
links |
An object containing the REST API URL for the account. |
To list all webhooks in an account:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/webhooks" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
response = client.webhooks
if response.success?
puts response.body["webhooks"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
client.listWebhooks()
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The webhooks property is an array of webhook objects.
{
"links": { ... },
"webhooks": [ ... ]
}
GET /v2/:account_id/webhooks
None.
To fetch a specific webhook:
curl "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/webhooks/WEBHOOK_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
webhook_id = 999999
response = client.webhook(webhook_id)
if response.success?
puts response.body["webhooks"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const webhookId = 111222;
client.fetchWebhook(webhookId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
The response looks like this:
# The webhooks property is an array of one webhook object.
{
"links": { ... },
"webhooks": [{ ... }]
}
GET /v2/:account_id/webhooks/:webhook_id
None.
To create a webhook:
curl -X POST "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/webhooks" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY: \
-d @- << EOF
{
"webhooks": [{
"post_url": "http://www.mysite.com/my-webhook-endpoint",
"events": [
"subscriber.created",
"subscriber.subscribed_to_campaign"
]
}]
}
EOF
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
pot_url = "https://www.mylistener.com/receieve"
include_received_email = false
events = [
"subscriber.created",
"subscriber.subscribed_to_campaign"
]
response = client.create_webhook(post_url, include_received_email, events)
if response.success?
puts response.body["webhooks"]
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const payload = {
webhooks: [{
post_url: "http://www.mysite.com/my-webhook-endpoint",
events: [
"subscriber.created",
"subscriber.subscribed_to_campaign"
]
}]
}
client.createWebhook(payload)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
201 Createdlike this:
{
"links": { ... },
"webhooks": [{
"id": "77777",
"href": "https://api.getdrip.com/v2/9999999/webhooks/77777",
"post_url": "http://www.example.com/api/v2/form",
"version": "1",
"include_received_email": false,
"events": [
"subscriber.created",
"subscriber.subscribed_to_campaign"
],
"created_at": "2013-06-21T10:31:58Z",
"links": {
"account": "9999999",
}
}]
}
POST /v2/:account_id/webhooks
| Key | Description |
|---|---|
post_url |
The url that the webhook will post to. |
include_received_email |
Optional. A Boolean specifying whether we should send a notification whenever a subscriber receives an email. Defaults to false. |
events |
Optional. An Array specifiying which events we should send notifications for. Eligible events can be found in the webhooks documentation. By default, we will send notifications for all events except subscrber.received_email. |
To destroy an existing webhook:
curl -X DELETE "https://api.getdrip.com/v2/YOUR_ACCOUNT_ID/webhooks/WEBHOOK_ID" \
-H 'User-Agent: Your App Name (www.yourapp.com)' \
-u YOUR_API_KEY:
require 'drip'
client = Drip::Client.new do |c|
c.api_key = "YOUR API KEY"
c.account_id = "YOUR_ACCOUNT_ID"
end
webhook_id = 999999
response = client.delete_webhook(webhook_id)
if response.success?
# ...
end
// npm install drip-nodejs --save
const client = require('drip-nodejs')({ token: YOUR_API_KEY, accountId: YOUR_ACCOUNT_ID });
const webhookId = 111222;
client.destroyWebhook(webhookId)
.then((response) => {
// Handle `response.body`
})
.catch((error) => {
// Handle errors
});
Responds with a
204 No Contentif successful.
DELETE /v2/:account_id/webhooks/:webhook_id
None.
Webhooks allow you to receive an HTTP POST to the URL(s) of your choosing anytime certain events occur in your Drip account. All our webhook events contain data serialized as JSON. If you'd like to be notified (with data) everytime a visitor subscribes, you'll want to use webhooks.
Adding a webhook URL is dead simple. Head over the Webhooks settings page, enter your URL, and hit "Create Webhook". That's it!
Because of the potential for high volume, we will not send notifications for
subscriber.received_email events by default. To receive a post every time a subscriber
receives an email, enable these events using the checkbox on the webhook's settings.
Please note that some webhook endpoints may implement rate limiting, especially for entry-level or free accounts. We will attempt to re-send webhooks for up to 3 days if they are rate limited. In addition, you will be notified and your webhook will be temporarily disabled if its endpoint generates any other error for 3 days.
{
"id": "z1togz2hcjrkpp5treip",
"status": "active",
"deliverable": true,
"email": "[email protected]",
"custom_fields": {
"shirt_size": "Medium"
},
"tags": ["Customer", "SEO"],
"time_zone": "America/Los_Angeles",
"utc_offset": -440,
"created_at": "2013-06-21T10:31:58Z"[Page truncated to keep GeezerVison snappy.]