LoopSolutions
All blueprints
Any platformReturn Create APIReturn ActionsBackend / automation engineer

Automate Return Creation

Create Loop returns programmatically — for any trigger where a return needs to exist but no shopper is at the keyboard. Scoped to refund returns for all eligible items, the sequence is 5 fixed calls plus a per-item loop of 3–5 calls. This blueprint walks the happy path, names the few values you hardcode, scales to bulk under Loop's rate limits, and closes the loop with Return Actions.

Before you start

  • API key with Draft Returns (read) and Draft Returns (write) scopes. To auto-process the resulting return via Return Actions, the same key also needs the Returns scope.
  • Return reasons configured in the Loop admin — you'll need their reason IDs at runtime.
  • A server-side orchestration layer. The Return Create API is HATEOAS-style: every response includes a links array of legal next actions, so you can't blindly pre-script the whole sequence.
  • An idempotency-key strategy you control. (shop_id, order_name, trigger_event_id) works well — track each trigger you've processed and skip it on retry. Loop prevents duplicate refunds for the same line item, so this is about keeping your own state clean.

The pieces you need

What you're wiring together, regardless of which path you take below.

Return Create client

Server
Server-side wrapper around the Return Create API endpoints. Holds your API credentials, drives the HATEOAS state machine, returns the draft_return_id from /draft-returns and threads it through to /submit.

Defaults config

Server
The handful of values you hardcode for the automation: the reason_id (parent + sub-reason if your taxonomy has children), the credit type (refund or gift), and the return method name to match against each draft's per-draft return_method_options. No business logic — just configuration.

Single-return orchestrator

Server
Sequences one return end-to-end: initialize → loop the eligible items adding reason + type per item → finalize → set credit type → set method → submit. Cancels half-built drafts on failure via /cancel.

Bulk orchestrator

Server
Wraps the single-return orchestrator for batch ops. Paces calls under Loop's 300-req/min-per-key limit, tracks idempotency, retries partial-batch failures, and logs the draft_return_id at every step.

Process-return caller (optional)

Server
Bridges to the Return Actions API. After /submit, calls Return Actions /warehouse/return/{id}/process to close out the return without manual review — the call that actually makes the automation hands-off.

The flow

What happens, in order, from cart change to Loop seeing the protected order.

  1. Initialize the draft return

    POST /draft-returns with shop_id, order_name, secondary_input (typically ZIP), and is_gift: false. Response returns the draft_return_id and context.items_eligible_to_return — the list you're about to loop over.
  2. For each eligible item, run three calls

    Default the automation to loop over every entry in items_eligible_to_return unless the trigger explicitly scopes to a subset. For each item, run these three calls in order before moving to the next item.
    1. Add the returning item
      POST /returning-items with the item's order_line_item_id. The response includes a new returning_item_id you'll use in subsequent calls.
    2. Answer any item-level user-input prompts
      Check the response links for add-returning-item-user-input-{user_input_id}-{returning_item_id} entries with is_required: true. These are merchant-configured prompts that fire on add-item (e.g. "Worn or washed?" — Yes/No). POST {"value": "..."} to each one before setting the reason or return type — required user inputs gate the rest of the per-item sequence.
    3. Set the return reason (+ sub-reason if required)
      POST /returning-items/{id}/return-reason with your hardcoded reason_id. Then check the response: if a set-returning-item-specific-return-reason-{id} link appears with is_required: true, the parent has children — fetch the sub-reason options from context.return_reason_options[returning_item_id] (Loop reuses the same field to surface children) and POST the chosen child reason_id to the same URL. Some reasons also surface a required set-returning-item-image-uploads-{user_input_id}-{returning_item_id} link (e.g. "Please upload a photo of the damaged item.") — pick reasons that don't, or build the upload path.
    4. Set the return type to `credit`
      POST /returning-items/{id}/return-type with "return_type": "credit". Always credit for this use case — exchanges aren't in scope for automation.
  3. Finalize items

    POST /finalize-items (empty body). Required even when there's nothing in the cart. Locks the return's contents before the credit-type decision.
  4. Set credit type

    POST /credit-type with refund (back to original payment) or gift (issue a gift card). Pick one in your defaults config — there's no item-by-item variant.
  5. Select the return method

    Read context.return_method_options from the response and POST /return-method with the return_method_id of the option you want. Required before submit. Match by name (e.g. purchase-label, happy-returns) since IDs are scoped per-draft and won't match across drafts.
  6. Submit

    POST /submit. Returns the finalized draft return; links is now empty. The return exists in Loop.
  7. (Optional) Process via Return Actions

    POST to Return Actions /warehouse/return/{id}/process to close out the return and trigger outcome execution — the call that makes the automation actually hands-off. Requires the Returns scope on the same key.

When to use this

Use this when an upstream event needs a Loop return to exist, and no shopper is at the keyboard. Canonical example: a fulfillment fails delivery and ships back to the warehouse — something has to create the return so the refund can execute. Other triggers fit the same shape: back-office RMA tools, CS workflows, OMS events, marketplaces without portal access.

Scope: refund or store-credit returns for all eligible items. Gift flow and exchanges are intentionally out — they pull in enough additional state that the automation becomes a portal. If you need those, talk to your Loop point of contact.

Not a portal substitute. The same APIs power custom shopper portals — a different problem shape (real-time UX, full feature surface) that warrants its own walkthrough.

How the API works

A HATEOAS state machine. Every response carries four blocks — draft_return (current state), context (data for the next call), links (legal next actions), errors. Between most steps you read links to know what's legal next, because the available actions depend on what you've done so far and how the merchant's admin is configured.

Call count: 5 fixed + 3–5 per item. The per-item variance comes from configurable prompts that may surface as required links:

  • After add-item: an item-level prompt like "Worn or washed?" (Yes/No).
  • After set-reason: a sub-reason chain, or an image-upload prompt ("Photo of the damaged item").

All required user inputs must be satisfied before /finalize-items succeeds.

The decisions you have to make

Scoped to refund returns for all eligible items, the decision space collapses to four values you hardcode. Everything else is sequencing the API calls. If you find yourself needing to vary these per-return, you're drifting toward portal territory — pause and rethink the scope.

The four values you hardcode for refund-return automation.
KeyTypeSourceExample
Return reason (parent)Why every item is being returnedConfigured reasons in the Loop adminreason_id: 50211 for "warehouse-initiated"
Return reason (sub-reason)More specific reason — same URL, new relChild reasons of the parent reasonPick one child if the parent requires it. Skip otherwise.
Credit typerefund or giftSet once per return after finalizerefund for the common warehouse-RMA case; gift for store-credit-only policies.
Return methodDrop-off / box-and-ship / etc. — requiredPer-draft context.return_method_optionsHardcode the name (e.g. purchase-label) and look up the matching id on each draft.

Initialize: the entry point

shop_id + order_name + secondary_input (typically the shopper's ZIP) identifies the order. is_gift: false for this use case.

Initialize a draft returnhttp
POST /api/v1/draft-returns
Content-Type: application/json
X-Authorization: <api-key>

{
  "shop_id": 23141001,
  "order_name": "#1001",
  "secondary_input": "90210",
  "is_gift": false
}

Scaling to bulk

Rate limit: 300 requests per minute per API key. At ~8–10 calls per return, that's roughly 30 returns/min/key serialized — fine for steady-state, tight for spikes. Three patterns to handle it:

  • Parallelize within a key. Use a token-bucket limiter to keep concurrent return-creations under 300/min total.
  • Pace, don't burst. Spread calls across the minute — bursting trips 429s for the rest of the window.
  • Gate on your own idempotency key (shop_id + order_name + trigger_id) before initializing, so retries skip cleanly and you don't leave abandoned drafts behind.

Closing the loop with Return Actions

/submit creates the return; it doesn't process it. To release the outcome (refund or store credit) without anyone touching the Loop admin, follow up with Return Actions:

POST /api/v1/warehouse/return/{return_id}/process

Successful process returns true. Retries on an already-closed return surface UNPROCESSABLE_RETURN in the response body — treat that as a retry-safe no-op (Loop won't issue duplicate refunds).

When to skip auto-process: inspection-required workflows (let physical inspection drive process/flag later), or fraud-flagged triggers (let Loop's standard review path catch it). See the Return Actions integration guide for the full surface.

Things to watch for

  • Always scan links for required user inputs after add-item and set-reason. Item-level prompts ("Worn or washed?") and reason-triggered prompts (damage photos) gate the rest of the per-item sequence. Skipping returns 422 on the next call.
  • Artifact IDs are per-draft. returning_item_id, return_method_id, and user_input_id are scoped to the specific draft. Match return methods by name and look up the id on each draft.
  • context.return_reason_options is reused for sub-reasons. Before setting the parent it holds parent reasons; after, it holds children for that item. Re-read between calls.
  • /finalize-items is required even with no cart items. POST it with an empty body.
  • Use your own idempotency key on init (shop_id + order_name + trigger_id). Loop prevents duplicate refunds, but tracking on your side keeps abandoned drafts off the floor.
  • The Returns scope is needed for /process. Draft Returns read/write covers creation only — add Returns to the same key for the Return Actions handoff.