# Implementing the RM API with AI

This page is meant to be pasted into a coding assistant (Claude, ChatGPT, Cursor, Copilot…). It has a **compact context**
of the API and **ready-to-copy prompts**. The full contract is `openapi.en.json` (OpenAPI 3.1): download it from
`/docs/openapi.en.json` and paste or attach it together with the prompt. For the business rules in full detail see
`/docs/api.en.md`.

Never paste a real key into a chat or a repository: use an environment variable (`RM_API_KEY`) and a fake value
in examples.

## Compact context

```text
RM REST API v1 (multi-tenant ecommerce). Base: {base_url} (e.g. https://retail-s.online/api/v1). JSON, UTF-8.

TENANT AND KEYS
- Each API key belongs to ONE store: everything it reads or writes belongs to that store. There is no way to pick another one.
- Authentication: header "Authorization: Bearer rk_<id>_<secret>". A back office session does not work.
- GET {base_url}/ returns the store, the key name and its scopes (connection test).

SCOPES (per area; GET requires read_<area>, POST/PUT/PATCH/DELETE require write_<area>; write includes read)
- products: products, variants, images, categories, brands, collections, attributes, templates, modifiers, translations, bulk creation, import.
- inventory: stock levels and locations. prices: prices and price lists. customers: customers and groups.
- orders: orders, statuses, payments, returns, refunds, carriers and payment methods.
- integrations: connections, sync runs and external references.
- Without the permission: 403 {"detail": "<message>"}.
- Error message texts are currently returned in Spanish: rely on the status code and on the field names, never on the text.

RECORD IDENTITY
- With a code (products, categories, brands, collections, attributes, locations, price lists, groups): the code, in uppercase (ply-001 = PLY-001).
- Variants: by SKU. Customers, orders, lines, returns, images, connections and external references: public UUID "id" (store it as a string).
- PUT /<resource>/<code>/ is an upsert (201 creates, 200 replaces). PATCH updates only what is sent (404 if it does not exist). The arrays "options", "variants" (with variants_mode "exact"), "addresses" and "attributes" are DECLARATIVE: what you send is what remains.
- Amounts and quantities: decimal strings ("199.0000"). Dates: ISO 8601 with a time zone.

PAGINATION AND SYNC
- Cursor lists: ?limit= (1 to 500, default 100). Response {"next": url|null, "results": [...]}. Request the "next" URL AS IS until it is null. There is no count, previous or offset (offset gives 400).
- Incremental: ?updated_since=<ISO 8601> on the code-based lists and on customers and orders; orders also ?placed_since=.
- Results of bulk jobs (bulk/import) are paginated with ?offset= and ?limit= on the job, not by cursor.

IDEMPOTENCY
- Optional header "Idempotency-Key" (up to 100 characters) on POST /orders/, POST /orders/{id}/pay/, /returns/ and /refunds/.
- Same key and same data: the operation is not repeated (201 the first time and 200 when repeated; pay is always 200). Same key with different data: 409.
- Without the header, a retry may create a duplicate (a repeated pay gives 409 "already paid").

RESPONSE CODES
- 200/201/204 success; 202 bulk job queued.
- 400 validation (details per field; in batches, {"errors": {"<index>": [...]}}). 401 missing, invalid or deactivated key. 403 missing scope. 404 does not exist in your store. 409 the step does not apply to the status, or Idempotency-Key reused with different data.
- 429: 600 requests per minute per key. Wait "Retry-After" seconds. Authenticated responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (epoch), except on 401 and 403.
- Retry with growing backoff only on 429, 5xx and network errors; never on a 4xx.

BULK OPERATIONS
- POST /prices/bulk/ and POST /inventory/bulk/: up to 1000 rows. All or nothing by default; ?partial=true applies the valid ones and returns 200 with "rows" per row. inventory on_hand is ABSOLUTE (what the ERP says), not an adjustment.
- POST /products/bulk/ (up to 500 products, upsert, 202 + job id; poll GET /products/bulk/{id}/).
- POST /products/import/ (multipart, NDJSON, create only, 202; result at GET /products/import/{id}/rows/).

ORDERS
- Separate statuses: status (open|cancelled|archived), payment_status, fulfillment_status; delivery_type (shipping|pickup|takeout|dine_in|delivery_app).
- POST /orders/ rejects (400) without stock or without a price. Changes: POST /orders/{id}/{pay,prepare,ship,ready,deliver,cancel,archive,unarchive,returns,refunds}/ (409 if the step does not apply).
- Inventory: each order reserves stock on creation; ship/ready deducts; cancel releases. /prepare/ only sets the informational stamp erp_accepted_at.

OUTGOING WEBHOOKS (RM -> your system; the store's "Webhooks" app)
- JSON POST to a public https URL: {"id", "event", "store", "occurred_at", "data"}.
- Events: order.created, order.updated, order.paid, order.payment_failed, order.shipped, order.ready_for_pickup, order.delivered, order.cancelled, order.returned, order.refunded, inventory.changed.
- Headers: X-RM-Event, X-RM-Event-Id (= id), X-RM-Signature = hex HMAC-SHA256 of the EXACT BODY (bytes, not re-serialized) with the app secret; User-Agent RM-Webhooks/1.
- Reply 2xx quickly (maximum wait 5 s). Retries on 5xx, 429, 408 or a network error: up to 8 in ~2 h, same body, same id, same signature. Any other 4xx is not retried.
- At least once and with no guaranteed order: deduplicate by id and compare occurred_at (inventory.changed: ignore anything older than the last one applied per SKU).
- After 5 consecutive failures the app is paused.
```

## Prompts

Each prompt says what to paste along with it. Always paste the **compact context** above and `openapi.en.json`.

### 1. Catalog and inventory connector from an ERP

Paste together with: the compact context, `openapi.en.json`, and a sample of 3 to 5 items from your ERP (JSON or CSV, with no real customer data).

```text
You are a senior integration engineer. Using the context and the OpenAPI of the RM API that I am attaching, write a connector
that syncs the catalog and inventory from my ERP to RM. Language: <Python 3.12 | Node 20>. Source ERP: <name>.

Requirements:
1. Read the key from the RM_API_KEY environment variable and the base URL from RM_BASE_URL. No secrets in the code.
2. First load: products through POST /products/bulk/ (batches of 500) or, if there are more than 20,000, POST /products/import/ with NDJSON. Poll
   the job until it finishes and save the rows with status "error" to an errors file.
3. Incremental load: use the ERP's updated_since; send PUT /products/{code}/ only for what changed. If the ERP already has variants, use variants_mode "exact".
4. Prices: POST /prices/bulk/?partial=true in batches of 1000, one per price list. Stock: POST /inventory/bulk/?partial=true with
   ABSOLUTE on_hand. Report each failed row without stopping the batch.
5. Keep the ERP <-> RM map with POST /external-references/ (object_type product or variant) and report each run with POST /connections/{id}/sync-runs/.
6. Retries: growing backoff on 429 (respect Retry-After), 5xx and network errors; never retry a 4xx. Respect the limit of 600 requests per minute.
7. Clear code, with logging that has no personal data, one test for each function that transforms data, and a README explaining how to run it.

Deliver: file structure, the complete code and the commands to test it against a test store. If the contract does not cover something, say so instead of inventing an endpoint.
```

### 2. Order webhook consumer with signature verification

Paste together with: the compact context and, if you want real examples, a test event body.

```text
Using the context of the RM API that I am attaching, write an HTTP receiver for order webhooks. Framework: <FastAPI | Express | Django>.

Requirements:
1. Verify X-RM-Signature: hex HMAC-SHA256 over the EXACT body received (raw bytes, not parsed or re-serialized)
   with the secret from RM_WEBHOOK_SECRET. Compare with a constant-time function. Invalid signature: 401 without processing.
2. Reply 2xx in under 5 s: store the event in a queue or table and process it separately.
3. Deduplicate by "id" (X-RM-Event-Id): the same event may arrive several times. Make the processing idempotent.
4. Do not assume order: compare occurred_at and discard an event older than the last one applied (in inventory.changed, per sku).
5. Handle order.created, order.paid, order.shipped, order.cancelled and order.refunded; ignore events you do not know, replying 2xx.
6. After a 2xx RM does not retry; on 5xx, 429 or a timeout it does. Tell temporary errors (5xx) apart from invalid data (4xx).
7. Tests: valid signature, invalid signature, repeated event and out-of-order event. Include a curl command that signs a sample body with openssl.

Deliver the complete code and the tests. Do not log the secret or the full order body.
```

### 3. Bulk import script

Paste together with: the compact context, `openapi.en.json` and a sample of 5 rows from your file (CSV or Excel) with the real columns.

```text
Using the context and the OpenAPI of the RM API that I am attaching, write a command-line script that imports my products file <CSV | XLSX> into RM.
Language: <Python 3.12 | Node 20>. I am giving you a sample of 5 rows.

Requirements:
1. Map the columns to the structure of PUT /products/{code}/ (code, name, status, category, options, variants, attributes...). Ask for confirmation of the mapping before sending.
2. --dry-run mode: validate and show how many products would be created and which rows are invalid, without calling the API.
3. Sending: POST /products/import/ (NDJSON) for large loads of new products; POST /products/bulk/ in batches of 500 if existing ones must be updated.
4. Poll the job until it finishes (with growing backoff) and write a report with the "error" rows and their message. Non-zero exit code if there were errors.
5. Key in RM_API_KEY and base URL in RM_BASE_URL; never in the code or in the report file.
6. It is resumable: it can run again without duplicating (use upsert by code or skip the ones already created).
7. Afterwards, optionally, images by URL (POST /products/{code}/images/ with an https "url") and stock with /inventory/bulk/.

Deliver the complete script, an example input file and the command to run it.
```

### 4. API client in Python or Node

Paste together with: the compact context and `openapi.en.json`.

```text
Using the OpenAPI and the context of the RM API that I am attaching, generate a small typed client for <Python 3.12 (httpx) | Node 20 (fetch) with TypeScript>.

Requirements:
1. A class RMClient(base_url, api_key) with the key in the Authorization: Bearer header.
2. One method per operation of the resources: products, variants, inventory, prices, customers and orders (including the actions pay, ship, cancel, returns and refunds).
3. Lists are iterators/generators that follow "next" as is until it is null; they accept limit and updated_since.
4. Retries with growing backoff on 429 (respect Retry-After), 5xx and network errors; no retries on 4xx. Typed errors: ValidationError (400, with details per field),
   AuthError (401/403), NotFound (404) and ConflictError (409).
5. Optional Idempotency-Key in create_order, pay_order, create_return and refund_order; generate a UUID if the caller does not provide one and reuse THAT SAME UUID on every retry of the same operation (a new one per operation, never a new one per retry).
6. Decimals as strings (not float); ISO 8601 dates.
7. Tests with a mock server for: pagination, 429 with Retry-After and 409 for a reused Idempotency-Key.

Deliver the client, the tests and a 20-line usage example. Do not include any real key.
```

### 5. Connector security review

Paste together with: the compact context and the full connector code (without the `.env` file).

```text
Act as a security reviewer. I am giving you the context of the RM API and the code of my connector. Review it and give me a prioritized list (critical, high, medium, low)
with file and line, the risk in one sentence and the concrete fix.

Look in particular for:
1. Secrets: API key (rk_...), webhook secret or ERP credentials in the code, in logs, in error messages, in git history or in example files.
2. Webhook receiver: HMAC-SHA256 signature over the raw body, constant-time comparison, rejection when unsigned, deduplication by id, body size limit and fast response.
3. Least privilege: the key must have only the scopes the connector uses (read_orders without write if it only reads orders, etc.).
4. Retries: none on 4xx, growing backoff on 429/5xx, respect for Retry-After, Idempotency-Key on operations that create or charge.
5. Validation of ERP data before sending it; error handling that neither loses data nor duplicates it; TLS verification on (never verify=False).
6. Customers' personal data in logs, temporary files or reports.
7. Outdated dependencies or dependencies without a pinned version.

Do not fix anything without showing me the finding first. If something in the RM contract is not clear to you, say so instead of assuming.
```
