Metadata-Version: 2.4
Name: medgrid-cli
Version: 0.1.0
Summary: MedGrid vendor integration CLI — push your catalog, prices, and stock to the MedGrid marketplace and receive order notifications.
Author: MedGrid
License: Proprietary
Project-URL: Documentation, https://medgrid.com/vendor-guide
Project-URL: Homepage, https://medgrid.com
Keywords: medgrid,vendor,marketplace,integration
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# medgrid — MedGrid Vendor CLI

The `medgrid` command line connects your systems to the MedGrid
marketplace: push your catalog, prices, and stock; track product approval;
receive and verify order notifications — no integration code required.

## Install

```sh
pip install medgrid-cli        # or: pipx install medgrid-cli
medgrid --version
```

Requires Python 3.9+; no other dependencies. To update later:
`pip install -U medgrid-cli`.

No pip in your environment? The tool is a single self-contained file —
download it directly and run it with any Python 3.9+:

```sh
curl -O https://medgrid.com/assets/medgrid/cli/medgrid
python3 medgrid --help
```

## Before you start

You need API credentials from MedGrid — three values, issued per
environment:

| Value | Purpose |
|---|---|
| `client_id` (`mgk_…`) | Public identifier for your vendor account |
| `client_secret` | Password for API authentication |
| `webhook_secret` | Key used to sign event notifications we send you |

They are shown exactly once when generated — store them securely. If lost,
MedGrid can rotate them (the `client_id` stays; both secrets change).

**Environments.** MedGrid runs two copies of the platform:

- **UAT** — `https://uat.medgrid.com` — test environment. Build and verify
  here first; orders never result in real shipments or charges.
- **Production** — `https://medgrid.com` — the live marketplace.

Credentials belong to exactly one environment: a UAT `client_id` does not
exist on production and vice versa. You'll receive UAT credentials to
integrate against, and a separate production set at go-live.

## Quick start

```sh
medgrid init                      # store credentials + verify (UAT is the default)
medgrid whoami                    # confirm who you're connected as
medgrid warehouses --create "Main Warehouse"
medgrid import products.csv --dry-run   # validate without sending
medgrid import products.csv
medgrid status SKU-001 SKU-002    # approval + publish state
medgrid webhook set-url https://yourdomain.com/medgrid-events
medgrid webhook test              # signed test event at that URL
medgrid orders                    # recent orders containing your items
```

`init` saves credentials to `./.medgrid/config.json` (file mode 600) and
immediately verifies them with a live connection test. Failures come back
in plain language — including "this environment has never issued this
client_id", the most common setup mistake. Access tokens are then obtained,
cached, and refreshed automatically; you never handle them.

Every command accepts `--env uat` or `--env prod`. UAT is the default;
production prints a visible banner before anything runs.

## Command reference

| Command | What it does |
|---|---|
| `init` | Store credentials for an environment and verify them. Flags: `--client-id`, `--client-secret`, `--webhook-secret`, `--no-verify` (all optional; prompts otherwise). |
| `whoami` / `auth` | Show the authenticated vendor, account class, permitted scope, and token expiry. `auth` forces a fresh session. |
| `check <client_id>` | No secret needed: asks each environment whether it issued this `client_id`. Use it when credentials are rejected and you're not sure why. Note: each probe counts one failed attempt toward that ID's lockout (10 failures per 15 min locks it temporarily). |
| `warehouses [--create NAME]` | List your registered warehouses, or register one. Required before stock uploads. |
| `import FILE` | Push a CSV of products, prices, or stock. See **CSV import** below. Flags: `--type products\|prices\|inventory` (otherwise guessed from the filename), `--dry-run`, `--batch-size N`. |
| `status SKU… [--file skus.txt]` | Approval and publish state for up to 500 SKUs: `Pending`/`Approved`, published or not. |
| `orders [--since DATE] [--status submitted\|cancelled] [--page N]` | Recent orders containing your items, newest first (default: last 7 days). Your safety net if your endpoint missed a notification. |
| `webhook set-url URL` | Register where MedGrid should deliver event notifications. |
| `webhook test` | Fire a real, signed test event at that URL right now and report how your endpoint answered. |
| `webhook verify <body-file\|-> --signature sha256=…` | Offline check that a payload you received was genuinely signed by MedGrid. |
| `listen [--port 9876]` | Local test receiver: prints each incoming event with its signature verdict while you build your real endpoint. Pair with a tunnel (e.g. ngrok) to receive UAT deliveries on your machine. |

Exit codes are honest: `0` only when everything succeeded, `1` otherwise —
safe to use in scripts and schedulers. Add `--json` to `warehouses`,
`status`, and `orders` for machine-readable output.

## CSV import

Column headers are case-insensitive and common synonyms are understood
(`title` → name, `category` → group, `item_code` → sku, …). Unrecognised
columns are ignored with a warning. Prices accept `$` signs and thousands
commas. Rows that fail validation are skipped with a line-numbered reason
and are never partially sent.

| Type | Columns | Required per row |
|---|---|---|
| `products` | `sku`, `vendor_product_id`, `name`, `description`, `group`, `price`, `cost`, `image` | `sku` or `vendor_product_id` |
| `prices` | `sku`, `price`, `price_list` | all three (`price_list` is the list name MedGrid assigns you) |
| `inventory` | `sku`, `warehouse`, `qty`, `snapshot_at` (optional) | `sku`, `warehouse`, `qty` |

Behavior you can rely on:

- **New products are not public.** Everything you push enters MedGrid's
  review queue (`Pending`) and appears on the marketplace only when
  approved. Track it with `medgrid status`.
- **Imports upsert.** An existing SKU is updated, not duplicated. If a SKU
  appears twice in one file, the later row wins.
- **Retries are safe.** Every batch carries an idempotency key derived
  from the file's content. Re-running the same file — after a network
  failure, a crash, even two runs at once — replays the original result
  instead of applying it twice. Never creates duplicates.
- **Large files are handled.** Rows are sent in batches of up to 500 (the
  API's cap). One bad row fails that row only, reported with its CSV line
  number.
- **Stale stock is rejected.** Inventory snapshots are timestamped; an
  older snapshot can never overwrite newer stock.

## Receiving notifications (webhooks)

MedGrid POSTs JSON events to your registered URL: `order.new`,
`order.cancelled`, `product.approved`, `product.rejected`, and
`webhook.test`. Each request carries:

```
X-MedGrid-Event:     order.new
X-MedGrid-Attempt:   1
X-MedGrid-Signature: sha256=<hex digest>
```

The signature is HMAC-SHA256 of the exact request body, keyed with your
`webhook_secret`. Always verify it before trusting a payload:

```python
import hashlib, hmac
expected = "sha256=" + hmac.new(webhook_secret.encode(), raw_body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, request.headers["X-MedGrid-Signature"])
```

Respond with any 2xx within 10 seconds. Failed deliveries are retried
3 times (after 1, 5, and 15 minutes). If your endpoint was down longer,
reconcile with `medgrid orders` — its payload mirrors the webhook body.

Recommended bring-up sequence: `medgrid listen` locally to see real signed
events → build your endpoint → `medgrid webhook set-url` → `medgrid
webhook test` → confirm your endpoint answers 200 and the signature
verifies.

## Limits

- **120 API requests per minute** per vendor. Exceeding it returns HTTP
  429 with a `Retry-After` header; the CLI surfaces the message directly.
  (A full catalog import is only a handful of requests — you're unlikely
  to hit this outside of tight polling loops.)
- **500 records per batch**, 500 SKUs per `status` call.
- **10 failed authentications per 15 minutes** locks a `client_id`
  temporarily.

## Troubleshooting

| Symptom | Meaning / fix |
|---|---|
| `this environment has never issued this client_id` | Your credentials belong to the other environment. Run `medgrid check <client_id>` to see which one issued them, then use `--env` accordingly. |
| `the client_id is known here, but the client_secret is wrong` | Re-copy the secret (watch for whitespace) or ask MedGrid to rotate credentials. |
| `the vendor profile is not Approved yet` / `API Enabled is not ticked` | Your MedGrid vendor profile hasn't finished onboarding — contact your MedGrid representative. |
| `temporarily locked out` | Too many failed authentications. Wait ~15 minutes; fix the secret before retrying. |
| `webhook test` fails with your endpoint's status code | Your receiver is reachable but rejecting the request — check the body parsing and that you respond 2xx. |
| `Unknown price list` on a prices import | Use the price list name MedGrid assigned to your account. |
| `An identical request is already in progress` | The same file is being imported concurrently; the CLI waits and retries automatically. |

Questions or credential requests: contact your MedGrid integration
representative.
