Create a buyer (Beta)
[!CAUTION] Beta. This endpoint is still being built: more options are being added, and what it accepts may change.
Not available yet, and still set in the application:
- Google Sheets delivery
- Static fields
- Ping caps and cap notifications
- Creating or editing buyer groups (listing them and assigning a buyer to one are supported)
- Deleting, cloning or importing a buyer
- Testing a buyer
- Reading back post conditions, payloads and headers (write-only)
Add a buyer to a campaign: which client it is, what it pays per lead, and how leads are delivered to it.
A new buyer starts paused, as it does in the application, so its setup can be checked before leads flow. Resume it with the pause & resume endpoint.
Delivery types
| delivery.type | What it does | Needs |
|---|---|---|
| lead_store | keeps the leads in Lead Prosper for the buyer to collect | nothing else; any other delivery property answers 422 |
| direct_post | sends each lead to the buyer's endpoint as it arrives | url |
| ping_post | asks the buyer first (ping), then sends the lead (post) | ping.url and post.url |
| one_to_one | a ping-post whose answers list several buyers, on a one-to-one campaign | ping.url, post.url and the buyers mapping on both |
| emails each lead | recipients |
An HTTP request
A direct_post is one request, written directly under delivery. A ping_post is two, under delivery.ping and delivery.post, each with the same properties. Only the url is required; what is left out gets the application's defaults:
| Property | Default | Rules |
|---|---|---|
| url | none | http or https. {{field_name}} tags are allowed in the path and the query string, not in the host. Private and loopback addresses are refused |
| method | POST | GET, POST or PUT. A GET sends the payload in the query string: its payload_type is form (or left out) and it takes no headers; anything else answers 422 |
| payload_type | form | form, json or xml |
| payload | generated | Must parse as its payload type. Generated as one {{field_name}} tag per campaign field, as the application's "generate payload" button does |
| headers | Content-Type for the payload type | A name holds letters, digits, -, _ and . only. Content-Type is added when none is given |
| timeout | 30 | 1 to 120 seconds |
| response_type | text | text, json or xml |
The deprecated <> tag syntax is refused in the url, the payload and the headers; write tags as {{field_name}}.
Reading the buyer's answer
response_mapping says what the answer looks like when the lead was accepted and when it was duplicated. A ping has accepted only.
| response_type | How a rule is read |
|---|---|
| text | the body is searched for value, case-insensitive. Defaults: accepted and duplicate |
| json, xml | key is a path in the body, compared with value by condition. All three are required |
| condition | Meaning | key |
|---|---|---|
| equal | the key equals the value. Matches true, false and null too | a path in the body |
| contain | the key contains the value | a path in the body |
| response_has | the whole body contains the value | fixed: body. Leave it out |
| http_status_code | the HTTP status equals the value | fixed: http_status_code. Leave it out |
Real-time pricing
With real_time_price on, the amount the buyer pays is read out of its answer at real_time_price_key. It needs a json or xml response, and pricing.static must not be sent with it: the price is saved as 0 and comes from the answer.
On a direct post and on the post leg it is refused where the price is decided another way: a highest-bidder campaign, a buyer in a highest-bidder group, a ping-post exchange, and a one-to-one campaign. The ping leg is not restricted.
Post conditions (ping-post)
delivery.post_conditions holds conditions the ping's answer must meet before the post is sent. Optional: without them the post is always sent. They need a json or xml ping response.
Groups are joined by match (any or all); so are the rules inside a group.
| Rule type | Compares | Conditions | Takes |
|---|---|---|---|
| field | a lead field | eq, neq, gt, gte, lt, lte, between, one_of, contains | field: a campaign field name, lp_subid1 or lp_subid2; value |
| ping_response | a key in the ping's answer | the same nine | field: the key; value |
| supplier | the supplier the lead came from | eq, neq, one_of | supplier_ids, from suppliers[].id |
| price | the price the ping returned | eq, gt, gte, lt, lte, between | value. Only with real-time pricing on the ping |
between takes two values, one_of a list, every other condition one. Post conditions are write-only for now: no response publishes them.
One-to-one
Only on a one-to-one campaign. A ping and a post, as a ping_post takes them, plus a buyers object on each that says where the answer lists the buyers:
| Property | On | Required | Meaning |
|---|---|---|---|
| list_key | ping, post | yes | the list of buyers in the answer |
| bid_id_key | ping, post | yes | a bid's id, inside each item |
| price_key | ping | yes | a bid's price, inside each item |
| tcpa_name_key | ping | yes | the buyer's TCPA name, inside each item |
| max_buyers | ping | no, default 0 (no limit) | the most bids taken from one answer. Only when the campaign sells shared |
The bids are priced by the ping's answer, so a one-to-one buyer takes no pricing.static, pricing.rules, pricing.bid_penalty, pricing.bid or pricing.supplier_margins, no real_time_price and no post_conditions. pricing.margin_override applies.
Customized API response (one-to-one campaigns)
api_response adds keys to each bid the supplier receives, on any buyer of a one-to-one campaign. A list of {"name": "...", "value": {"type": "...", "value": "..."}, "ping": true, "post": true}, replaced as a whole when sent; null removes it.
| value.type | The key's value is | Delivery types |
|---|---|---|
| static | value.value itself | all |
| ping_response | read from the ping's answer at value.value | ping_post, one_to_one |
| post_response | read from the post's answer at value.value. Returned on the post only | direct_post, ping_post, one_to_one |
| ping_response_buyers, post_response_buyers | read from each buyer's item in the answer | one_to_one |
id, price, execution_time and buyer_id are in every bid already and cannot be names. tcpa_name is the one predefined key: it needs a value, is returned on both the ping and the post, takes static (and ping_response or post_response on a ping_post), and is not available on a one_to_one buyer, which reads it at buyers.tcpa_name_key. Changing a buyer to one_to_one removes it.
| Property | Default | Rules |
|---|---|---|
| recipients | none | 1 to 5 addresses, no duplicates |
| subject | New lead from {{lp_campaign_name}} | may carry tags |
| from_name | the platform's sender name | |
| reply_to | the platform's reply-to address | a valid address |
| payload | generated | the email body; one name: {{name}} line per field when omitted |
Conditional prices
pricing.rules is a list of conditional prices. For each lead they are checked in order: the first whose rules match sets the price, and pricing.static applies when none does. Not accepted with real-time pricing, and turning real-time pricing on removes them.
"pricing": {
"static": 10,
"rules": [
{"price": 25, "match": "all", "rules": [
{"type": "field", "field": "state", "condition": "one_of", "value": ["CA", "NY"]},
{"type": "field", "field": "loan_amount", "condition": "gte", "value": "50000"}
]},
{"price": 15, "match": "any", "rules": [
{"type": "day_time", "windows": ["sat|12:00 AM|12:00 AM|America/New_York", "sun|12:00 AM|12:00 AM|America/New_York"]}
]}
]
}
Here a California or New York lead asking for 50,000 or more pays 25, any other lead on a weekend pays 15, and everything else pays 10.
| Key | Meaning |
|---|---|
| price | what the lead pays when this item matches |
| price_from_field | instead of price: read it from a lead field of type numeric (any other type answers 422). A lead with no number there falls through to the next item |
| match | all (default): every rule must match. any: one is enough |
| rules | at least one rule, of the types below |
Rule types
| type | Matches when | Takes | Example |
|---|---|---|---|
| field | the lead's value in a field meets the condition | field: a campaign field name, lp_subid1 or lp_subid2; condition; value | {"type": "field", "field": "state", "condition": "eq", "value": "CA"} |
| supplier | the lead came from this supplier. Buyers only | supplier_id, from suppliers[].id | {"type": "supplier", "supplier_id": 779} |
| day_time | the lead arrives inside one of the windows | windows | {"type": "day_time", "windows": ["mon|09:00 AM|05:00 PM|America/New_York"]} |
With match: all a field can be used once per item.
Conditions (field rules)
| condition | Matches when the lead's value | value | Example |
|---|---|---|---|
| eq | equals the value | one | {"field": "state", "condition": "eq", "value": "CA"} |
| neq | differs from the value, or is empty | one | {"field": "state", "condition": "neq", "value": "CA"} |
| gt | is greater than the value | one | {"field": "loan_amount", "condition": "gt", "value": "50000"} |
| gte | is the value or greater | one | {"field": "credit_score", "condition": "gte", "value": "700"} |
| lt | is less than the value | one | {"field": "loan_amount", "condition": "lt", "value": "10000"} |
| lte | is the value or less | one | {"field": "debt", "condition": "lte", "value": "5000"} |
| between | is from the first value to the second, both included | two | {"field": "credit_score", "condition": "between", "value": ["600", "699"]} |
| one_of | is any value in the list | a list | {"field": "zip_code", "condition": "one_of", "value": ["90210", "10001"]} |
| exclude | is none of the values in the list. An empty value does not match | a list | {"field": "state", "condition": "exclude", "value": ["FL", "TX"]} |
| contains | contains the text anywhere | one | {"field": "email", "condition": "contains", "value": "@gmail."} |
| blank | is empty or missing | none | {"field": "phone", "condition": "blank"} |
| nblank | has any value | none | {"field": "phone", "condition": "nblank"} |
Text comparisons ignore case. An empty value matches only blank and neq.
Which conditions a field takes depends on its type, from the campaign's fields:
| Field type | Conditions | value |
|---|---|---|
| text (and lp_subid1, lp_subid2) | eq, neq, one_of, exclude, contains, blank, nblank | text; on a field with options, one of its options |
| numeric | eq, neq, gt, gte, lt, lte, between, one_of, exclude, blank, nblank | numbers |
| date | the same as numeric | dates in the field's own format |
| date of birth | eq, neq, gt, gte, lt, lte, between, blank, nblank | age in years: {"field": "date_of_birth", "condition": "gte", "value": "18"} is 18 or older |
Day and time
day_time windows are written as a DAY_HOUR filter's (see Delivery schedule windows): day|start|end|timezone, 12-hour times on the quarter hour, one timezone for all windows, no overlaps. Two differences: a window cannot run past midnight (split it at 12:00 AM into two days, fri|10:00 PM|12:00 AM|... and sat|12:00 AM|02:00 AM|...), and start equal to end is the whole day.
Bid override (highest bidder)
On a highest-bidder campaign or in a highest-bidder buyer group, pricing.bid changes where the buyer stands when buyers are ordered by bid. It never changes what the buyer pays.
| pricing.bid.mode | Takes | The buyer is ordered as if its bid were |
|---|---|---|
| custom | price, optional rules (same shape as pricing.rules) | the amount entered. Refused when identical to the buyer's price |
| bump_up | percentage, above 0 and up to 1000 | its bid plus the percentage |
| bump_down | percentage, above 0 and up to 100 | its bid minus the percentage |
| position | position, 1 to 999 | at that place in line, 1 being first |
"bid": null inside pricing turns it off. The bid penalty is a separate setting, applied on top of the override; it has no effect with position.
Supplier-specific margins (ping-post exchange)
pricing.supplier_margins sets a margin on this buyer's bids for chosen suppliers: {"enabled": true, "suppliers": [{"supplier_id": 779, "margin": 40}]}. It has its own switch, independent of pricing.margin_override, and the list is kept while it is off. A margin for a supplier wins over that supplier's own margin override.
Settings that depend on the campaign
Each is refused with 422 on a campaign where the application does not show it.
| Property | Applies on |
|---|---|
| pricing.bid_penalty | a highest-bidder campaign, a buyer in a highest-bidder group, or a ping-post exchange that is not one-to-one |
| pricing.margin_override | a ping-post exchange or a one-to-one campaign |
| pricing.supplier_margins | a ping-post exchange |
| delivery.type: one_to_one, api_response | a one-to-one campaign |
| pricing.bid | a highest-bidder campaign, or a buyer in a highest-bidder group |
| buyer_group_id | a campaign that distributes by buyer groups; ids from GET /v2/campaigns/{campaign}/buyer-groups. A buyer in no group receives no leads |
Full examples
One complete body per delivery type. Field names such as state and phone are the campaign's own; use yours.
Lead store
{
"client_id": 50,
"nickname": "Acme Buyer - Lead store",
"pricing": {"static": 8},
"delivery": {"type": "lead_store"}
}
Direct post, paying 20 for California and New York leads and 12.50 for the rest:
{
"client_id": 50,
"nickname": "Acme Buyer - Direct post",
"pricing": {
"static": 12.5,
"rules": [
{"price": 20, "rules": [{"type": "field", "field": "state", "condition": "one_of", "value": ["CA", "NY"]}]}
]
},
"delivery": {
"type": "direct_post",
"url": "https://example.com/leads?source={{lp_supplier_id}}",
"method": "POST",
"payload_type": "json",
"payload": "{\"first_name\": \"{{first_name}}\", \"last_name\": \"{{last_name}}\", \"phone\": \"{{phone}}\", \"state\": \"{{state}}\", \"zip\": \"{{zip_code}}\"}",
"headers": [{"name": "Authorization", "value": "Bearer abc123"}],
"timeout": 30,
"response_type": "json",
"response_mapping": {
"accepted": {"key": "status", "condition": "equal", "value": "ok"},
"duplicated": {"condition": "response_has", "value": "duplicate"}
}
}
}
Ping post. The buyer answers the ping with {"accepted": true, "ping_id": "…", "bid": 12}; the post is sent only when bid is 10 or more, and carries the ping_id back:
{
"client_id": 50,
"nickname": "Acme Buyer - Ping post",
"pricing": {"static": 15},
"delivery": {
"type": "ping_post",
"ping": {
"url": "https://example.com/ping",
"payload_type": "json",
"payload": "{\"state\": \"{{state}}\", \"zip\": \"{{zip_code}}\"}",
"timeout": 10,
"response_type": "json",
"response_mapping": {
"accepted": {"key": "accepted", "condition": "equal", "value": "true"}
}
},
"post": {
"url": "https://example.com/post",
"payload_type": "json",
"payload": "{\"ping_id\": \"{{lp_ping_response:ping_id}}\", \"first_name\": \"{{first_name}}\", \"last_name\": \"{{last_name}}\", \"phone\": \"{{phone}}\"}",
"timeout": 30,
"response_type": "json",
"response_mapping": {
"accepted": {"key": "status", "condition": "equal", "value": "ok"},
"duplicated": {"key": "status", "condition": "equal", "value": "duplicate"}
}
},
"post_conditions": {
"groups": [
{"rules": [{"type": "ping_response", "field": "bid", "condition": "gte", "value": "10"}]}
]
}
}
}
{
"client_id": 50,
"nickname": "Acme Buyer - Email",
"pricing": {"static": 5},
"delivery": {
"type": "email",
"recipients": ["leads@example.com", "sales@example.com"],
"subject": "New lead from {{lp_campaign_name}}",
"from_name": "Acme Leads",
"reply_to": "support@example.com",
"payload": "Name: {{first_name}} {{last_name}}\nPhone: {{phone}}\nState: {{state}}"
}
}
Request
- Base URL: https://api.leadprosper.io/public
- URL: https://api.leadprosper.io/public/v2/campaigns/{campaign}/buyers
- Auth: one of:
- HTTP bearer
- API key in query parameter api_token
Path parameters
Campaign id.
Request body
Example request
{
"client_id": 50,
"nickname": "Acme Buyer - Direct post",
"pricing": {
"static": 12.5,
"rules": [
{
"rules": [
{
"windows": [
"mon|09:00 AM|05:00 PM|America/New_York"
]
}
]
}
],
"bid": {
"rules": [
{
"rules": [
{
"windows": [
"mon|09:00 AM|05:00 PM|America/New_York"
]
}
]
}
]
}
},
"api_response": [
{
"name": "source"
}
],
"delivery": {
"url": "https://example.com/leads",
"headers": [
{
"name": "Authorization",
"value": "Bearer …"
}
],
"real_time_price_key": "data.price",
"response_mapping": {
"accepted": {
"key": "status",
"value": "ok"
},
"duplicated": {
"key": "status",
"value": "ok"
}
},
"ping": {
"url": "https://example.com/leads",
"headers": [
{
"name": "Authorization",
"value": "Bearer …"
}
],
"real_time_price_key": "data.price",
"response_mapping": {
"accepted": {
"key": "status",
"value": "ok"
}
},
"buyers": {
"list_key": "bids",
"bid_id_key": "id",
"price_key": "price",
"tcpa_name_key": "name"
}
},
"post": {
"url": "https://example.com/leads",
"headers": [
{
"name": "Authorization",
"value": "Bearer …"
}
],
"real_time_price_key": "data.price",
"response_mapping": {
"accepted": {
"key": "status",
"value": "ok"
},
"duplicated": {
"key": "status",
"value": "ok"
}
},
"buyers": {
"list_key": "bids",
"bid_id_key": "id"
}
}
}
}Response
Success
Changes
No recorded changes to this operation across all 1 revision of this API.