Skip to content

Retries and duplicates

Open .md
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.

Every JSON answer has a success field as well as its HTTP status. Read them together:

  1. 2xx and "success": trueIt happened: don’t retry. After publishing, also check data.valid
  2. 200 and "success": falseRefused, and nothing changed, except Delete a job (below). Read error
  3. 4xxIt didn’t happen. Fix the request before you send it again. Retry a 429 after waiting
  4. 5xx, a timeout or no answerYou don’t know. Read the record back before you retry, and fix the cause of a known business 500 first
A few refusals answer 200, so the HTTP status alone isn't enough.

The 200 refusals and the business 500s are listed under Known exceptions.

  1. 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.

  2. Send the create.

  3. If the answer is 5xx, or you got no answer, look for the record. For an order, search for its PO number:

    GET Search sales orders and quotes /v2/api/store/{team_slug}/search/orders
  4. 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.

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.

POST Create or update a customer by 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 with contact as its only contact. The contact’s is_primary field is ignored on creation. Set it afterwards if needed (see Create and sync customers).

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, generating or ready, 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: 422 with duplicate_report and 429 with export_capacity.
  • The same key for a different export is refused with 422.

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.

POST Create a shipment for a sales order /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: the revision_number you last read. If someone changed the address since, YoPrint refuses the save with 409 (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 a 409.
PUT Update a customer address /v1/api/store/{team_slug}/customer/{hash_id}/address/{id}

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.

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 4xx without changing the request, except a 429: it will fail the same way.
  • Don’t retry the business 500s listed on Errors: fix the cause.