shopify-admin-return-cost-attribution
SkillCommerce & financeRead-only: calculates the true cost of returns by reason and product — refund dollars, restocking impact, shipping cost lost, and COGS impact for items written off.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the shopify-admin-return-cost-attribution skill
What this skill tells your AI
The instructions your AI receives, as published by 40rty-ai/shopify-admin-skills in skills/returns/shopify-admin-return-cost-attribution/SKILL.md and read by ahel’s review.
Purpose
Quantifies the full cost of returns over a window — not just the refunded amount. Combines refund totals, lost shipping revenue, COGS for non-restockable items (e.g., DEFECTIVE), and restocking labor into a per-reason and per-product return P&L. Read-only. Use to prioritize which reasons or product lines deserve operational fixes — better packaging, size guides, listing accuracy.
Prerequisites
shopify store auth --store <domain> --scopes read_orders,read_returns,read_inventory- API scopes:
read_orders,read_returns,read_inventory
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| store | string | yes | — | Store domain (e.g., mystore.myshopify.com) |
| format | string | no | human | Output format: human or json |
| days_back | integer | no | 90 | Lookback window for returns |
| group_by | string | no | reason | Aggregation level: reason, product, sku, or reason_x_product |
| min_returns | integer | no | 3 | Minimum returns per group to include in summary |
| writeoff_reasons | array | no | ["DEFECTIVE"] | Return reasons whose items are treated as non-restockable (full COGS write-off) |
| flat_restocking_cost | float | no | 5.00 | Average labor cost per return line item to model restocking workload |
Safety
ℹ️ Read-only skill — no mutations are executed. Cost figures are estimates derived from
unitCost, refund totals, andflat_restocking_cost— calibrate the flat-cost figure to your operation before treating outputs as accounting truth.
Workflow Steps
-
OPERATION:
returns— query Inputs:query: "created_at:>='<NOW - days_back days>'",first: 250, select returns with line item pricing, product/variant,inventoryItem.idExpected output: All returns in window with per-line-item pricing -
OPERATION:
orders— query Inputs: For each return'sorder.id, fetchrefunds { totalRefundedSet refundLineItems { quantity subtotalSet totalTaxSet lineItem { id } } }Expected output: Refund amounts mappable to line items -
OPERATION:
inventoryItems— query — batch uniqueinventoryItem.idfrom step 1; returnsunitCostper item -
Per line item compute:
refund_amount(matched refundLineItem proportional to returned qty),shipping_loss(order shipping × line-item value share for full-order returns; else 0),cogs_writeoff(unitCost × qtyonly ifreturnReason in writeoff_reasons),restocking_labor(flat_restocking_cost × qty). Sum and aggregate bygroup_by.
GraphQL Operations
# returns:query — validated against api_version 2025-01
query ReturnsForCostAttribution($query: String!, $after: String) {
returns(first: 250, after: $after, query: $query) {
edges {
node {
id
status
createdAt
totalQuantity
order {
id
name
totalShippingPriceSet { shopMoney { amount currencyCode } }
totalPriceSet { shopMoney { amount currencyCode } }
}
returnLineItems(first: 50) {
edges { node {
id
quantity
returnReason
fulfillmentLineItem { lineItem {
id
title
quantity
discountedTotalSet { shopMoney { amount currencyCode } }
originalUnitPriceSet { shopMoney { amount currencyCode } }
variant { id sku inventoryItem { id } }
product { id title vendor }
} }
} }
}
}
}
pageInfo { hasNextPage endCursor }
}
}
# orders:query — validated against api_version 2025-01
query OrderRefundsForReturns($query: String!, $after: String) {
orders(first: 250, after: $after, query: $query) {
edges {
node {
id
name
refunds {
id
createdAt
totalRefundedSet { shopMoney { amount currencyCode } }
refundLineItems(first: 50) {
edges { node {
quantity
subtotalSet { shopMoney { amount currencyCode } }
totalTaxSet { shopMoney { amount currencyCode } }
lineItem { id }
} }
}
}
}
}
pageInfo { hasNextPage endCursor }
}
}
# inventoryItems:query — validated against api_version 2025-01
query InventoryUnitCosts($ids: [ID!]!) {
nodes(ids: $ids) {
... on InventoryItem {
id
unitCost { amount currencyCode }
tracked
}
}
}
Session Tracking
Claude MUST emit the following output at each stage. This is mandatory.
On start, emit:
╔══════════════════════════════════════════════╗
║ SKILL: Return Cost Attribution ║
║ Store: <store domain> ║
║ Started: <YYYY-MM-DD HH:MM UTC> ║
╚══════════════════════════════════════════════╝
After each step, emit:
[N/TOTAL] <QUERY|MUTATION> <OperationName>
→ Params: <brief summary of key inputs>
→ Result: <count or outcome>
On completion, emit:
For format: human (default):
══════════════════════════════════════════════
RETURN COST ATTRIBUTION (<days_back> days, group: <group_by>)
Returns analyzed: <n>
Total return cost: $<amount> (refund <pct>%, shipping <pct>%, COGS <pct>%, labor <pct>%)
Top cost drivers:
<group> Returns: <n> Total: $<n> Avg: $<n> Top reason: <reason>
Output: return_cost_<date>.csv
══════════════════════════════════════════════
For format: json, emit:
{
"skill": "return-cost-attribution",
"store": "<domain>",
"period_days": 90,
"group_by": "reason",
"returns_analyzed": 0,
"totals": {
"total_cost": 0, "refund": 0, "shipping_loss": 0,
"cogs_writeoff": 0, "restocking_labor": 0, "currency": "USD"
},
"groups": [],
"output_file": "return_cost_<date>.csv"
}
Output Format
CSV file return_cost_<YYYY-MM-DD>.csv with columns:
group_key, return_count, units, refund_amount, shipping_loss, cogs_writeoff, restocking_labor, total_cost, avg_cost_per_return, top_return_reason, currency
Error Handling
| Error | Cause | Recovery |
|---|---|---|
THROTTLED | API rate limit exceeded | Wait 2 seconds, retry up to 3 times |
Missing unitCost | Cost not recorded | Treat COGS as 0 and flag the row |
| Refund not yet processed | Customer not yet refunded | Use line item discounted total as estimate |
| Multiple refunds per return | Partial refund history | Sum refunds tied to the return's line items |
| No shipping cost | Free shipping | Shipping loss = 0 |
Best Practices
- Use
group_by: reason_x_productto surface lethal combos likeDEFECTIVE × <hero SKU>— supplier-quality issues addressable at the source. - Re-run after
unitCostupdates; stale cost most often skews COGS write-off. - Pair with
return-reason-analysisto compare "what returns most" with "what costs most" — they often diverge.
Signals
- GitHub stars
- 187
- Forks
- 18
- Last commit
- Aug 2026
ahel review
S4info
community integration — published by 40rty-ai, not shopify
Automated review, not a security audit. Ruleset v1.
Advanced
- Catalog kind
- skill
- Gateway key
shopify-admin-return-cost-attribution- Source
- github.com/40rty-ai/shopify-admin-skills