> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sourcemedium.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Medusa - Integration Instructions

> Connect your Medusa store to SourceMedium with a read-only reporting projection.

## Follow this integration guide to connect your Medusa data to SourceMedium.

Medusa is an open-source, self-hosted commerce platform. SourceMedium connects to your Medusa store through a **read-only reporting API** that your developer exposes on your Medusa backend, secured with a publishable API key. SourceMedium polls this API to sync your orders, customers, products, inventory, and subscriptions.

<Snippet file="connect-managed-note.mdx" />

### How it works

<Steps>
  <Step title="Your developer enables the reporting projection">
    Share the [projection requirements](#projection-requirements-for-your-developer) below with your developer. They expose the `/reporting` routes on your Medusa backend and confirm each requirement with the self-test checklist.
  </Step>

  <Step title="Share your credentials with SourceMedium">
    Send your SourceMedium team two values (via your account manager or [integrations@sourcemedium.com](mailto:integrations@sourcemedium.com) — not in public channels):

    * Your **Medusa publishable API key** (starts with `pk_`)
    * Your **Medusa backend base URL** (for example, `https://store.yourdomain.com`)

    Our team enters the credential in SourceMedium's connection manager, the same secure store used for every API-key integration. Because Medusa uses a merchant-built reporting projection rather than a standard connector, it is set up with our team rather than in the self-serve picker.
  </Step>

  <Step title="SourceMedium validates and backfills">
    We run a live validation against your projection. If anything is missing, we tell your developer exactly which route and field to fix. Once validation passes, we run the full historical backfill and your data starts syncing automatically.
  </Step>
</Steps>

<Snippet file="api-key-security-note.mdx" />

### What data SourceMedium reads

* **Orders**, including line items, shipping lines, fulfillments, and refunds
* **Customers**
* **Products and variants**
* **Inventory** items and levels
* **Subscriptions**, when your store runs a subscription module that exposes them

### Projection requirements (for your developer)

Expose a `/reporting` API on the store's Medusa backend meeting this exact contract. Every requirement below comes from a production-validated integration — meeting them up front means validation passes on the first try.

#### 1. Authentication

* Requests carry the store's **publishable API key** in the `x-publishable-api-key` header.
* The projection must reject requests without a valid key.

#### 2. Routes and response envelope

Expose these five GET routes:

| Route                      | Returns                                                                                              |
| -------------------------- | ---------------------------------------------------------------------------------------------------- |
| `/reporting/orders`        | Orders with inline children (see below)                                                              |
| `/reporting/customers`     | Customer records                                                                                     |
| `/reporting/products`      | Products with nested `variants[]` (including `prices[]`)                                             |
| `/reporting/inventory`     | Inventory items with nested `location_levels[]`. Envelope key is `inventory_items` (not `inventory`) |
| `/reporting/subscriptions` | Subscription records (only if the store has subscriptions)                                           |

Every route must:

* Support **offset/limit pagination**: `?limit=200&offset=0` (`limit` up to 200).
* Respond with the envelope `{ "<resource>": [...], "count": N, "offset": n, "limit": l }`, where `count` is the total number of records. The `<resource>` key matches the route name for every route except `/reporting/inventory`, whose key is `inventory_items`.
* Return records in a **stable sort order** (for example, `created_at` descending) so paginated reads are consistent.

#### 3. Order filtering (required for backfill)

`/reporting/orders` must accept `created_from` and `created_to` ISO-8601 query params and filter **server-side**, both bounds **inclusive**:

```
GET /reporting/orders?created_from=2025-03-01T00:00:00.000Z&created_to=2025-04-01T00:00:00.000Z&limit=200
```

SourceMedium uses these to backfill history in 30-day windows. Without them, historical backfill is not possible.

#### 4. Order payload

Each order must include all of the following. Fields marked ⚠️ are the ones most commonly missed — each was a real issue that blocked or delayed a past onboarding.

| Field                                                                                                                            | Requirement                                                                                                                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`, `status`, `created_at`, `updated_at`                                                                                       | Always populated. `status` = pending / completed / canceled.                                                                                                                                                                                      |
| `payment_status`, `fulfillment_status`                                                                                           | ⚠️ Populated on every order that has them.                                                                                                                                                                                                        |
| `canceled_at`                                                                                                                    | Populated on canceled orders.                                                                                                                                                                                                                     |
| `currency_code`                                                                                                                  | ISO currency code.                                                                                                                                                                                                                                |
| Money totals: `total`, `subtotal`, `tax_total`, `discount_total`, `shipping_total`, `item_total`, `paid_total`, `refunded_total` | ⚠️ **Computed real values on every order, including historical ones** — not `0` and not `null`. Major currency units (`6990` = ₱6,990.00 or \$6,990.00).                                                                                          |
| Capture details: `paid_total`, `net_captured`, `over_captured`, `captures[]`                                                     | ⚠️ `captures[]` is the payment capture ledger (empty array `[]` when nothing is captured, not missing).                                                                                                                                           |
| `items[]`                                                                                                                        | ⚠️ Line items inline on **every order, including the oldest historical orders**. Each item: `id`, `quantity` (ordered, non-null), `unit_price` (pre-discount), `subtotal` (pre-discount), `total` (post-discount), `tax_total`, `discount_total`. |
| `shipping_methods[]`                                                                                                             | Shipping lines inline, each with its `amount`.                                                                                                                                                                                                    |
| `fulfillments[]`                                                                                                                 | Fulfillments inline, including tracking details and which line items were fulfilled.                                                                                                                                                              |
| `refunds[]`                                                                                                                      | ⚠️ Refunds as a **top-level array on the order** (not nested inside payments), each with `id`, `amount`, `currency_code`, `created_at`.                                                                                                           |
| `metadata`                                                                                                                       | Included as-is. If recurring orders link to subscriptions through metadata keys, preserve those keys.                                                                                                                                             |

#### 5. Subscriptions (only for stores with subscriptions)

* `/reporting/subscriptions` returns all subscription records with `created_at` and `updated_at`.
* There must be a way to link a recurring order to its subscription — typically a subscription ID in the order's `metadata`. The exact metadata keys are merchant-specific; SourceMedium reviews them during onboarding.
* Note: subscription history only covers what your subscription module has synced into Medusa. If your provider started syncing recently, SourceMedium reports from that point forward.

#### 6. Self-test checklist

Before handing off to SourceMedium, verify all of the following with `curl`:

```bash theme={null}
# 1. Auth works
curl -H "x-publishable-api-key: pk_YOUR_KEY" https://YOUR_BASE_URL/reporting/orders?limit=1

# 2. Pagination envelope is present (count/offset/limit)
# 3. Date filter works — a narrow window returns fewer orders:
curl -H "x-publishable-api-key: pk_YOUR_KEY" \
  "https://YOUR_BASE_URL/reporting/orders?created_from=2025-03-01T00:00:00.000Z&created_to=2025-03-07T00:00:00.000Z&limit=200"
```

Then confirm, by inspecting real responses:

* [ ] A **recent completed order** has non-zero `total`, `subtotal`, `item_total`, and non-empty `items[]` with non-null `quantity`.
* [ ] An order from your **oldest month of business** also has computed money totals and non-empty `items[]` (not header-only).
* [ ] A **refunded order** has a top-level `refunds[]` array with the refund `amount`.
* [ ] A **canceled order** has `canceled_at` populated.
* [ ] An order with a **partial capture** has `captures[]` entries and `net_captured` set.
* [ ] `/reporting/products` includes `variants[]` with `prices[]`.
* [ ] `/reporting/inventory` includes `location_levels[]`.
* [ ] All five routes reject a request with a missing or invalid key.

### Data coverage notes

* **Returns and exchanges** are not currently collected for Medusa stores.
* **Marketing attribution**: Medusa has no built-in customer-journey source, so attribution models receive no Medusa-native input.
* After the initial backfill, SourceMedium syncs your Medusa data automatically every 6 hours.

### Troubleshooting

* **Validation failed on money fields?** The projection is returning `0` or `null` instead of computed totals — this is the most common issue. Your developer must compute totals on the backend, including for historical orders.
* **Revenue looks off by 100x?** Currency units were not confirmed. Contact your account manager to re-verify whether your projection emits major or minor units.
* **Old orders have no line items?** The projection is only hydrating `items[]` for recent orders. It must hydrate them for all history.
* **Need to rotate a key?** Create a new publishable key in your Medusa Admin (**Settings → Publishable API Keys**) and share it with your SourceMedium team; we update the stored credential in our connection manager with no data loss.
