Shipping rules
Shipping rules turn your shipping policy into something Ship Jay enforces. A rule says: when these conditions hold, do these actions. Rules run on every quote, whether it comes from the app, a spreadsheet, the API or an AI agent. A rate a rule removes is removed from the quote itself, so it can never be bought.
The API manages the same rules you see in Settings, Shipping rules. Reading needs rules:read. Creating, replacing and deleting need rules:write.
| Call | What it does |
|---|---|
GET /rules |
List your rules. |
POST /rules |
Create a rule. Returns 201 and { "id": "..." }. |
PUT /rules/{id} |
Replace a rule. Send the full rule. |
DELETE /rules/{id} |
Delete a rule. |
Anatomy of a rule
Section titled “Anatomy of a rule”{ "name": "Sign for high value parcels", "phase": "shipment", "priority": 100, "enabled": true, "stop_on_match": false, "conditions": { "all": [ { "field": "declared_value_cents", "op": "gte", "value": 20000 } ] }, "actions": [ { "type": "require_signature" }, { "type": "set_insurance", "amount_cents": 20000 } ]}| Field | Meaning |
|---|---|
name |
1 to 80 characters. |
phase |
shipment runs before rating. rate runs on the rates that came back. |
priority |
1 to 10000, default 100. Lower numbers run first. |
enabled |
Default true. |
stop_on_match |
When true, a rule that matches ends its phase. Later rules in that phase are skipped. |
conditions |
A group, { "all": [...] } or { "any": [...] }. Groups can nest. Default is { "all": [] }, which always matches. An empty any never matches. |
actions |
1 to 10 actions. Which ones are allowed depends on the phase. |
Fields
Section titled “Fields”A condition compares a field to a value. Text comparisons ignore case and surrounding spaces.
| Field | Type | Phases |
|---|---|---|
to.country |
text, two letter code | shipment, rate |
to.state |
text | shipment, rate |
to.zip |
text | shipment, rate |
international |
true or false, true when the country is not US | shipment, rate |
weight_oz |
number | shipment, rate |
packaging_type |
text | shipment, rate |
declared_value_cents |
number, the larger of the insurance amount and the customs value | shipment, rate |
reference1 |
text | shipment, rate |
rate.carrier |
text | rate only |
rate.service |
text | rate only |
rate.price_cents |
number | rate only |
rate.days |
number, estimated delivery days | rate only |
A shipment rule that uses a rate. field is rejected with 422.
Operators
Section titled “Operators”| Operator | Meaning |
|---|---|
eq, neq |
equals, does not equal |
gt, gte, lt, lte |
numeric comparisons |
in, not_in |
the value is in, or not in, a list. Send value as an array of up to 50 entries. |
starts_with, contains |
text comparisons |
If a rate has no estimated delivery time, rate.days has no value and the condition only holds for neq and not_in.
Actions
Section titled “Actions”Shipment phase
| Action | Effect |
|---|---|
{ "type": "block", "message": "..." } |
The quote fails with 422 and your message. details.blockedByRule is true. |
{ "type": "require_signature" } |
Turns on signature on delivery. |
{ "type": "set_insurance", "amount_cents": 20000 } |
Insures the parcel. Between 100 and 500000 cents. The highest amount among matching rules wins. |
Rate phase
| Action | Effect |
|---|---|
{ "type": "exclude_carrier", "carrier": "UPS" } |
Removes rates from that carrier. |
{ "type": "exclude_service", "contains": "Express" } |
Removes rates whose service name contains the text. |
{ "type": "max_price", "cents": 3000 } |
Removes rates above that price. |
{ "type": "max_days", "days": 5 } |
Removes rates estimated to take longer. Rates with no estimate stay. |
{ "type": "choose_rate", "strategy": "cheapest" } |
Picks a rate and returns it as suggested_rate_id. Strategies: cheapest, fastest, best (a balance of price and speed). Add "carrier" to pick only from one carrier. |
In rate rules, the conditions are tested against each rate. choose_rate chooses among the rates that match the conditions and survived earlier removals. The first rule to choose wins, and the chosen rate gets the rule badge. A rule that chooses a rate does not buy it. Your code still has to call buy.
Worked example
Section titled “Worked example”Goal: sign for and insure expensive parcels, never use UPS for parcels over 50 lb, and suggest the cheapest option that arrives within five days.
curl https://shipjay.com/api/v1/rules \ -H "Authorization: Bearer $SHIPJAY_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sign for high value parcels", "phase": "shipment", "priority": 10, "conditions": { "all": [ { "field": "declared_value_cents", "op": "gte", "value": 20000 } ] }, "actions": [ { "type": "require_signature" }, { "type": "set_insurance", "amount_cents": 20000 } ] }'
curl https://shipjay.com/api/v1/rules \ -H "Authorization: Bearer $SHIPJAY_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "No UPS over 50 lb", "phase": "rate", "priority": 10, "conditions": { "all": [ { "field": "weight_oz", "op": "gt", "value": 800 } ] }, "actions": [ { "type": "exclude_carrier", "carrier": "UPS" } ] }'
curl https://shipjay.com/api/v1/rules \ -H "Authorization: Bearer $SHIPJAY_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Cheapest within five days", "phase": "rate", "priority": 50, "conditions": { "all": [ { "field": "rate.days", "op": "lte", "value": 5 } ] }, "actions": [ { "type": "choose_rate", "strategy": "cheapest" } ] }'const rules = [ { name: "Sign for high value parcels", phase: "shipment", priority: 10, conditions: { all: [{ field: "declared_value_cents", op: "gte", value: 20000 }] }, actions: [{ type: "require_signature" }, { type: "set_insurance", amount_cents: 20000 }], }, { name: "No UPS over 50 lb", phase: "rate", priority: 10, conditions: { all: [{ field: "weight_oz", op: "gt", value: 800 }] }, actions: [{ type: "exclude_carrier", carrier: "UPS" }], }, { name: "Cheapest within five days", phase: "rate", priority: 50, conditions: { all: [{ field: "rate.days", op: "lte", value: 5 }] }, actions: [{ type: "choose_rate", strategy: "cheapest" }], },];
for (const rule of rules) { const res = await fetch("https://shipjay.com/api/v1/rules", { method: "POST", headers: { Authorization: `Bearer ${process.env.SHIPJAY_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify(rule), }); console.log(res.status, await res.json());}rules = [ { "name": "Sign for high value parcels", "phase": "shipment", "priority": 10, "conditions": {"all": [{"field": "declared_value_cents", "op": "gte", "value": 20000}]}, "actions": [{"type": "require_signature"}, {"type": "set_insurance", "amount_cents": 20000}], }, { "name": "No UPS over 50 lb", "phase": "rate", "priority": 10, "conditions": {"all": [{"field": "weight_oz", "op": "gt", "value": 800}]}, "actions": [{"type": "exclude_carrier", "carrier": "UPS"}], }, { "name": "Cheapest within five days", "phase": "rate", "priority": 50, "conditions": {"all": [{"field": "rate.days", "op": "lte", "value": 5}]}, "actions": [{"type": "choose_rate", "strategy": "cheapest"}], },]
for rule in rules: res = requests.post( "https://shipjay.com/api/v1/rules", headers={"Authorization": f"Bearer {os.environ['SHIPJAY_KEY']}"}, json=rule, timeout=30, ) print(res.status_code, res.json())Quote a 60 lb parcel after this and the UPS rates are gone. applied_rules in the quote response lists each rule that did something, with its effects, and suggested_by_rule names the rule that chose a rate.
© 2026 Ship Jay. All rights reserved. Created & Developed by WCWS Internet
