Retries and duplicates
AI agents: read /llms.txt; add .md to any page URL. This page as Markdown: /concepts/retries-and-duplicates.md. Index of every page and the rules for every call: /llms.txt. Guides and concepts in one file: /llms-full.txt. OpenAPI: /openapi.json.
Which requests are safe to repeat
Section titled “Which requests are safe to repeat”| Request | Safe to repeat? |
|---|---|
GET, and POST filters and searches |
Yes. They only read. |
PUT or PATCH that sets fields to values |
Usually. Repeating it sets the same values again. |
DELETE |
Yes. A repeat either answers 404 or succeeds without changing anything, depending on the operation. Deleting an order line twice, for example, answers 200 with action_performed: "NONE". |
POST that creates a record |
No. A repeat creates another one. |
| Anything that sends an email or text, records or moves money, or buys a label | No. A repeat sends, records or buys again. Repeated sends also count toward the email limits. |
Know whether a change happened
Section titled “Know whether a change happened”Every JSON answer has a success field as well as its HTTP status. Read them together:
- 2xx and "success": trueIt happened: don’t retry. After publishing, also check
data.valid - 200 and "success": falseRefused, and nothing changed, except Delete a job (below). Read
error - 4xxIt didn’t happen. Fix the request before you send it again. Retry a
429after waiting - 5xx, a timeout or no answerYou don’t know. Read the record back before you retry, and fix the cause of a known business
500first
The 200 refusals and the business 500s are listed under Known exceptions.
Retry a create without a duplicate
Section titled “Retry a create without a duplicate”-
Before the first attempt, decide how you’ll find the record later. Use something only this record has and that a read can find, such as the customer’s PO number on an order, which order search matches. For customers, see Find a customer by email.
-
Send the create.
-
If the answer is
GET Search sales orders and quotes5xx, or you got no answer, look for the record. For an order, search for its PO number:/v2/ api/ store/ {team_slug}/ search/ orders -
If it’s there, use it. If it isn’t, send the create again.
Keep a record of each create you send and its outcome, so a restart of your integration doesn’t send the same create twice.
Built-in ways to avoid duplicates
Section titled “Built-in ways to avoid duplicates”Customers and contacts: upsert
Section titled “Customers and contacts: upsert”Instead of creating a customer, upsert it. With "perform_upsert": true, YoPrint looks among your team’s live customers for one with a contact whose email or phone matches the contact you send. If it finds one, it updates that customer. Otherwise it creates the customer and the contact.
/v1/api/store/{team_slug}/customer/upsert
{ "perform_upsert": true, "name": "Northwind Apparel", "contact": { "first_name": "Alex", "last_name": "Rivera", "email": "alex@example.com", "send_invoice": true, "is_primary": true }}Contacts work the same way inside one customer:
POST Create or update a customer contact by email or phone/v1/api/store/{team_slug}/customer/{hash_id}/contact/upsert
The dangerous request
This doesn’t create “Acme Corp”: it renames the first customer it finds.
{ "perform_upsert": true, "name": "Acme Corp", "contact": { "first_name": "Jordan", "last_name": "Lee", "send_invoice": true, "is_primary": true }}Rules to know before using upsert:
- It matches on email or phone. A contact with the same phone number on another customer is treated as a match.
- When a customer matches, every top-level field you send (
name,website,tax_exempt,vat_number, etc.) overwrites that customer’s values. Fields you leave out keep their existing values. - When a customer matches, the contact itself is not updated, and no new contact is added to the customer.
- When nothing matches (or with
perform_upsert: false), a new customer is created withcontactas its only contact. The contact’sis_primaryfield is ignored on creation. Set it afterwards if needed (see Create and sync customers).
Data exports: a request key
Section titled “Data exports: a request key”The data export operations take an idempotency_key in the body, up to 100 characters.
- Sending the same key again returns the same export, and starts nothing new, while it’s
pending,generatingorready, or after it failed while generating. - If the export failed to queue, or its attempt expired, the same key queues it again. The usual limits then apply:
422withduplicate_reportand429withexport_capacity. - The same key for a different export is refused with
422.
Shipments: an operation key
Section titled “Shipments: an operation key”Creating a shipment takes an optional operation_key, up to 100 characters. Sending the same key with the same body returns the shipment already made instead of a second one. The same key with a different body is refused.
/v2/api/store/{team_slug}/v2/sales_order/{unique_key}/shipment
Customer addresses: an operation key and a revision
Section titled “Customer addresses: an operation key and a revision”Saving a customer’s address takes two optional fields:
base_revision_number: therevision_numberyou last read. If someone changed the address since, YoPrint refuses the save with409(stale_address) instead of overwriting their change.operation_key, your own id for the save: retrying with the same key and the same values returns the address the first try made, instead of a409.
/v1/api/store/{team_slug}/customer/{hash_id}/address/{id}
Shipping labels
Section titled “Shipping labels”YoPrint’s only guard is a 10-second lock per rate, counted from the first attempt’s start. It’s cleared at once when a purchase succeeds, kept for the rest of the 10 seconds after a failure, and it expires while a slow purchase is still running. After that, Shippo may sell a single-parcel rate again. Wait for each purchase’s answer, and read the shipment’s packages to see whether the label was bought before you retry. See Ship an order.
Retry with backoff
Section titled “Retry with backoff”For reads, and for changes you’ve confirmed didn’t happen:
- Wait before the first retry, and wait longer each time, for example 1, 2, 4 and 8 seconds.
- Stop after a few attempts and report the failure.
- Don’t retry a
4xxwithout changing the request, except a429: it will fail the same way. - Don’t retry the business
500s listed on Errors: fix the cause.
