Rule reference¶
A rule, which the API calls a line item, places one or more offers at
one or more moments of an agent, for the customers and situations it
describes. This page is the reference for the line item object of the
Offer Manager API: its fields, the condition grammar
of audience and exclusions, the order in which the
Fetch API evaluates a rule, and how writes are validated. For
the concept, see Rules.
Rules are managed in the lineItem collection:
POST https://api.advertising.chat/system/offer-manager/lineItem
PATCH https://api.advertising.chat/system/offer-manager/lineItem/<id>
Fields¶
| Field | Type | Description |
|---|---|---|
name |
string, 1 to 200 characters | Required. The rule's display name. |
application |
ID | Required. The agent the rule belongs to. See Application scoping. |
enabled |
boolean | Required. Whether the rule serves. |
adUnits |
array of IDs, at least one | Required. The moments the rule applies to, all of the rule's application. |
creatives |
array of IDs | The offers the rule can show, in order. A house rule needs at least one. An exchange rule has none. |
priority |
6, 8, 10, or 12 |
6 is High, 8 is Default, and 10 is Low. A lower number wins. Required on a house rule. 12 is the priority of every exchange rule. |
audience |
condition group | Who sees the rule's offers. Absent means everyone. |
exclusions |
condition group | Who doesn't. A customer the group matches is removed. Absent means no one is excluded. |
channels |
array of rcs, sms, webchat, messenger, voice |
The channels the rule serves on. Absent or empty means every channel. See Channels. |
schedule |
object | When the rule serves, in the org's time zone. Absent means always. See Schedule. |
frequency |
{ times, interval } |
The most times one customer is shown the rule within a rolling window. Absent means no cap. See Frequency. |
limits |
array of limits | Caps on the whole rule. See Limits. |
fallback |
boolean | Makes the rule a fallback rule, considered only when no other rule qualifies. |
testMode |
boolean | Marks the rule's responses with isTest: true. |
creativeSelectionStrategy |
default or sequential |
How the rule picks among its offers. sequential shows them to each customer in the order of creatives, and needs a subscriber on the request. |
dailyBudget |
number, 0 or more | A daily budget that paces the rule evenly across the UTC day. Absent or 0 means unpaced. |
orderType |
house or exchange |
house, the default, shows the rule's offers. exchange asks an ad network for one. |
adNetworkCode |
string, 1 to 100 characters | Exchange rules only. The ad network asked to fill. Absent means the default network. |
The platform writes three fields that a request can't set:
| Field | Description |
|---|---|
reporting.impressions |
The impressions the rule has served, which the impressions limit reads. |
_description |
{ summary, policyHash, generatedAt }: a generated plain-language summary of the rule, and the hash of the rule it describes. |
_provenance |
Per policy field (adUnits, creatives, priority, audience, exclusions, channels, schedule, frequency), { instruction, author, surface, policyHash, at }: the plain-language instruction the field was last written from. surface is console, copilot, cli, mcp, or api. |
When policyHash differs from the rule's current hash, the summary or the
instruction describes an earlier version of the rule or field.
Instructions¶
A create or update can send instructions beside the rule's fields: per
policy field, the plain-language instruction that the field's new value was
translated from, 1 to 4,000 characters. The platform records each one in
_provenance with the field's hash. Send null for a field that was
written by hand to remove its entry. instructions isn't stored on the
rule.
{
"audience": { "all": [{ "attribute": "plan", "op": "equals", "value": "prepaid" }] },
"frequency": null,
"instructions": { "audience": "prepaid customers", "frequency": null }
}
Application scoping¶
Every rule and every moment belongs to one application, set when it is
created and fixed afterwards, and a rule holds only its own application's
moments. The Fetch API resolves the application from the fetch key or the
application parameter before it looks up the moment. See
Application scoping and
How the application and moment are resolved.
Exchange rules¶
An exchange rule (orderType: "exchange") is a placeholder that asks an
ad network for an offer at its moments. It has no creatives (absent or
empty), and its priority is 12, below every house priority. When
priority is absent it is 12, and any other value is rejected.
adNetworkCode names the network, and only an exchange rule may set it.
Every other field applies to an exchange rule as it does to a house rule:
its audience, exclusions, channels, schedule, frequency, and limits all
restrict it. Because it has no offers of its own, its conditions are
evaluated once, and a history condition on this_offer never matches.
Channels¶
channels restricts a rule to the channels it lists: rcs, sms,
webchat, messenger, and voice. The request's channel is the Fetch API's channel
parameter, or the channel its render format names. See
Channels.
A rule that lists channels never matches a request whose channel is
unknown, such as a direqt request with no channel parameter. A rule
with no channels, or an empty list, matches every request, including
those with no channel.
Schedule¶
schedule limits when a rule serves. Each property is checked against the
current time in the org's time zone, which is the Time zone setting on
the org's Settings page, or UTC when it isn't set.
| Property | Value | Matches when |
|---|---|---|
start |
YYYY-MM-DD |
Today is on or after the date. |
end |
YYYY-MM-DD |
Today is on or before the date. It can't be before start. |
daysOfWeek |
1 to 7 of mon, tue, wed, thu, fri, sat, sun |
Today is one of the days. |
hours |
{ from: 0..23, to: 1..24 } |
The current hour is at or after from and before to. |
A schedule sets at least one property. When from is greater than to,
the hours run overnight: { "from": 22, "to": 6 } is 10 PM to 6 AM. The
window must be shorter than the whole day, so from and to can't be
equal, and { "from": 0, "to": 24 } is rejected; omit hours instead.
{ "schedule": { "start": "2026-11-01", "daysOfWeek": ["fri", "sat"], "hours": { "from": 17, "to": 23 } } }
Frequency¶
frequency allows at most times serves of the rule to one customer
within any rolling interval:
| Property | Value |
|---|---|
times |
A whole number from 1 to 20. |
interval |
Milliseconds, from 60,000 (one minute) to 7,776,000,000 (90 days). |
{ "times": 2, "interval": 604800000 } shows the rule to a customer at
most twice in any seven days. Moments and offers carry a cap of the same
shape. The serves are read from subscriber history,
which keeps 20 events per type for 95 days; that is where the bounds come
from. A request without a subscriber has no history, so no frequency cap
applies to it.
Limits¶
Each entry of limits is { limitType, value, enabled }. An entry with
enabled: false is ignored.
limitType |
value |
Effect |
|---|---|---|
impressions |
A whole number, 0 or more. | The rule stops qualifying once reporting.impressions reaches the value. |
percentage |
A whole number from 0 to 100. | The rule qualifies for that percentage of requests, chosen at random. |
Conditions¶
audience and exclusions share one grammar. Each is a condition
group, which holds conditions and at most one level of nested groups:
group := { "all": [item, …] } | { "any": [item, …] } 1 to 20 items
item := condition | nested group a nested group holds conditions only
condition := attribute condition | history condition
An all group matches when every item matches, and an any group matches
when at least one does. audience admits the customer when its group
matches, and exclusions removes the customer when its group matches.
Nesting stops at a depth of two, which covers "A and (B or C)" and
"(A and B) or (C and D)":
{
"audience": {
"all": [
{ "attribute": "tier", "op": "equals", "value": "gold" },
{ "any": [
{ "attribute": "nights", "op": "gte", "value": 3 },
{ "attribute": "loyaltyMember", "op": "true" }
] }
]
}
}
Attribute conditions¶
An attribute condition, { attribute, op, value }, tests one of the
attributes sent on the fetch
request. attribute is the key exactly as the agent sends it, 1 to 128
characters, and must name an entry in the
attribute catalog of the rule's agent that isn't retired. The
operator must apply to the entry's type (see Types),
and for an enum, every value must be one of the entry's values. The shape
of value depends on op:
op |
value |
Matches when the attribute |
|---|---|---|
equals |
A string, number, or boolean | Equals the value. |
not_equals |
A string, number, or boolean | Doesn't equal the value, or is missing. |
in |
A list of 1 to 100 strings, numbers, or dates as YYYY-MM-DD |
Equals one of the values. A date matches on the same day. |
not_in |
Same as in |
Equals none of the values, or is missing. |
gt, gte, lt, lte |
A number, or a date as YYYY-MM-DD |
Is greater than, at least, less than, or at most the value. |
between |
[low, high], two numbers or two dates, low not above high |
Is between the two, inclusive. |
contains |
One string or number | Is a list that contains the value. |
within_last |
Whole days, 1 to 3650 | Is a date within that many days up to today. 7 is today and the six days before it. |
more_than_ago |
Whole days, 1 to 3650 | Is a date earlier than that many days before today. |
exists |
None | Is present and readable. |
not_exists |
None | Is missing or unreadable. |
true, false |
None | Reads as that boolean. |
String values are at most 500 characters. Numbers must be finite.
How attributes are read¶
Every attribute arrives from the request as sent, often as a string. It is
read as the type of its entry in the agent's
attribute catalog. So { "attribute": "renewal", "op":
"equals", "value": "2026-12-01" } on a date attribute matches
2026-12-01T18:30, the same calendar day, and on a string attribute it
doesn't. An enum reads as a string.
| Type | How a value is read |
|---|---|
| String | Compared with loose equality against the value sent: the number 5 equals "5", and "05" doesn't equal "5". |
| Number | Read from the start of the value, so "10%" reads as 10. |
| Boolean | true, false, "true", "false", "yes", "no", "1", "0", 1, and 0, in any case. |
| Date | Compared as calendar days in the org's time zone. A date (2026-05-01) or a local date and time (2026-05-01T10:00) names the day written. A date and time with an offset or Z is converted to the org's time zone first. A number is epoch milliseconds. |
| List | A JSON array, or a comma-separated string with each item trimmed. |
Today, for within_last and more_than_ago, is the current day in the
org's time zone, so the agent doesn't send the current date.
Two states decide how a condition treats an attribute that can't be compared:
- Missing means the request has no such key or sends
null. For number, boolean, and date reads, an empty string is also missing.not_equals,not_in, andnot_existsmatch a missing attribute, and every other operator doesn't. So{ "attribute": "plan", "op": "not_equals", "value": "unlimited" }includes customers whose plan is unknown. - Unreadable means present but not readable as its type, such as
"abc"for a number,"soon"for a date, or"maybe"for a boolean. Onlynot_existsmatches an unreadable attribute.
History conditions¶
A history condition, { event, subject, within_days, at_least }, tests the
customer's past offer events within the rule's agent, as recorded in
subscriber history:
| Property | Value |
|---|---|
event |
shown, declined, accepted, or converted. |
subject |
this_offer, the offer being considered; offer:<id>, one offer; rule:<id>, one rule; or any_offer, every offer of the agent. |
within_days |
A rolling window ending now, from 1 to 90 days. |
at_least |
The number of events required, from 1 to 20. Defaults to 1. |
The condition matches when the subject has at least at_least events of
that type in the window. A decline is a tap on a reply with the decline
role, or an offer_declined event.
Silence is not a decline: a customer who doesn't answer has no declined
event. A request without a subscriber has no history, so its history
conditions don't match.
Conditions on this_offer are evaluated for each of the rule's offers in
turn, so an exclusion can remove one offer and leave the rule's other
offers eligible.
How a rule is evaluated¶
For each request, the Fetch API first checks the moment's own frequency
cap; a customer who has reached it gets an empty response. It then checks
each enabled rule of the moment in this order. The first check a rule fails
removes it, and the
decision trace records that check as
the rule's reasonCode:
limit: the rule'slimits. Fallback rules are set aside.channel: the request's channel must be one of the rule'schannels.schedule: the current time must fall within the rule'sschedule.audienceandexclusions: evaluated once for each offer. An offer passes when the audience matches and the exclusions don't. The rule continues with the offers that passed, and the trace lists the removed ones inoffersExcluded. If no offer passes, the rule is removed, and the trace records the deciding condition incondition.format: the rule needs an offer that can render in the requested format.- If no rule remains, the fallback rules go through steps 1 to 5.
frequency: the customer must be under the rule'sfrequencycap.- The highest priority among the remaining rules wins, and the winning rule picks one of its eligible offers that is under the offer's own cap.
The full request flow, including context and the appropriateness check, is in How a request is decided.
Validation¶
Every create and update is validated against the rule model. A request that
fails returns 422 with a message naming each problem and a
propertyErrors list of { property, message }. Validation rejects:
- An unknown field, including the retired
targets. Targeting isaudienceandexclusions. - A value outside its type or bounds, such as a
priorityof7, afrequency.timesof30, or abetweenwhose first value exceeds the second. - A house rule with no
creativesor nopriority, or with priority12or anadNetworkCode. - An exchange rule with
creatives, or with a priority other than12. - An attribute that isn't in the catalog of the rule's application, or is retired, and a condition whose operator or values don't fit the attribute's type.
- A history condition that names an offer outside your org, or a rule of another application.
- A moment of another application, or an offer that isn't in your org.
- An
applicationthat differs from the rule's application, on an update.
An update is validated twice: as sent, and merged with the stored rule. The merged rule must be a valid rule, so an update that makes a house rule into an exchange rule must also clear its offers.
To clear an optional field on an update, send it as null. These fields
can be cleared: audience, exclusions, channels, schedule,
frequency, limits, dailyBudget, and adNetworkCode. Sending null
for any other field returns 422. A field that the update omits is
unchanged.
Examples¶
Create a rule¶
The resort concierge agent from Moments, offers, and rules offers a spa booking after check-in to gold-tier guests on RCS, at most once a week:
{
"name": "Spa for gold guests",
"application": "6650a1f0c3b2a1d4e5f60718",
"enabled": true,
"adUnits": ["6650a1f0c3b2a1d4e5f60720"],
"creatives": ["6650a1f0c3b2a1d4e5f60730"],
"priority": 8,
"audience": { "all": [ { "attribute": "tier", "op": "equals", "value": "gold" } ] },
"channels": ["rcs"],
"frequency": { "times": 1, "interval": 604800000 }
}
Exclude anyone who turned the offer down in the last 30 days¶
{ "exclusions": { "any": [ { "event": "declined", "subject": "this_offer", "within_days": 30 } ] } }
Sent in a PATCH, this adds the exclusion and leaves the rest of the rule
unchanged. After 30 days without another decline, the customer sees the
offer again.
Exclude recent buyers and guests checking out soon¶
{
"exclusions": {
"any": [
{ "event": "converted", "subject": "any_offer", "within_days": 14 },
{ "attribute": "checkOutDate", "op": "lte", "value": "2026-11-03" }
]
}
}
Clear fields¶
This PATCH removes the channel restriction and the schedule:
Create an exchange rule¶
{
"name": "Network ads after resolution",
"application": "6650a1f0c3b2a1d4e5f60718",
"enabled": true,
"orderType": "exchange",
"adUnits": ["6650a1f0c3b2a1d4e5f60721"],
"channels": ["webchat"]
}
The rule takes priority 12 and asks the default ad network for an offer.