Appearance
Surfaces
Interactive API Explorer: Explorer
Base: https://api.g3nretailstack.com/mrs. Router trims API Gateway stage (/prod) and prefixes /mrs if missing, so always call the custom-domain base. Auth: provide either a human USM session via header x-session-guid or a service-account API key via header x-api-key (bearer secrets; not accepted via query string). orgcode required, cccode optional. Caller must belong to the requested org (owner or active member); non-associated callers receive 404 not-found (anti-enumeration). container is normalized to lowercase and must match ^[a-z][a-z0-9_-]{1,79}$ (2..80 chars). Default pagination limit 8 (clamp 1–256); next_token opaque. Tags: [0-9A-Za-z]{1,128}, cap 20, canonical uppercase. Inline JSON only when content_type=application/json and payload ≤256 KB (no gzip). Presigned uploads mandatory for >256 KB or non-JSON; gzip required; 128 MB hard cap. content_md5 must be the hex MD5 of the gzip payload on both presign and complete. Downloads via presigned GET except inline JSON. Record status FSM: pending_upload → active → doomed (terminal). Presigned uploads start in pending_upload and transition to active on successful record/complete; inline JSON creates go directly to active. Records in pending_upload are not readable via GET /mrs/record (returns invalid-state). Doomed retained 365d (S3 versions); default queries exclude doomed unless specified. Idempotency: optional idempotency_key (ASCII ≤128) scoped to container+record_id or container+key; 24h window; replays return the first response (errors included). Scheduled jobs are internal (EventBridge → Lambda); operator-only maintenance ops are direct Lambda (not API Gateway).
Surface Types (explicit)
API Gateway
- Status: Available.
- Base:
https://api.g3nretailstack.com/mrs - Notes: Primary tenant surface for records, lists, and presigned flows.
Direct Lambda
- Status: Available (operator-only).
- Notes: Maintenance operations such as reconcile/tag-scrub/cleanup (IAM-gated).
CLI
- Status: Available.
- Command:
g3n mrs ...(API Gateway + operator direct Lambdas). - Notes: See
cli/README.mdfor operator-only commands.
MCP
- Status: Available.
- Canonical protocol:
https://mcp.g3nretailstack.com/mrs/PROTOCOL.md - Mirror:
https://doc.g3nretailstack.com/mrs/PROTOCOL.md
Auth + tenancy
- Required identity:
orgcode(query string for GET; JSON body for POST). - Auth headers:
x-session-guid(user session) orx-api-key(service account). Query-string auth is not accepted. - Optional cost attribution:
cccode(query or body where supported). - Non-associated callers receive
404 not-found(anti-enumeration).
Role enforcement
All MRS API Gateway routes require specific roles from OFM member/resolve. Missing roles return 403 mrs.role_required.
| Route | Required roles |
|---|---|
GET /record, GET /record/meta, GET /list, GET /head | mrs_reader or mrs_writer |
POST /tag/add, POST /tag/remove | mrs_writer |
POST /record (put), POST /record/complete, POST /doom, POST /ttl/set | mrs_writer |
Identifier policy
- Direct get/update/status calls require GUID/ID fields (
*_idor legacy*_guidwhere that is the canonical field name). Code-based lookups are resolve/search only. - Responses never include both
*_idand*_guidfor the same record (no dual-field output). - Exceptions (email-based UAS, PVM resolve, MRS
container+record_id) are listed in /common/ids-codes.html.
Request builder (canonical)
Headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"Field placement
- GET:
orgcode,container, and selectors go in the query string. - POST: parameters go in the JSON body.
Retry guidance (endpoint class)
- GET reads are safe to retry with identical inputs.
- POST writes should only be retried with an
idempotency_key(or when you can tolerate duplicates).
GET template
bash
curl -sS -G "https://api.g3nretailstack.com/mrs/record/meta" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "container=demo" \
--data-urlencode "record_id=rec-1"POST template
bash
curl -sS -X POST "https://api.g3nretailstack.com/mrs/record" \
-H "content-type: application/json" \
-H "x-session-guid: $SESSION_GUID" \
-d '{"orgcode":"'$ORGCODE'","container":"demo","content_type":"application/json","payload":{"hello":"world"}}'Field placement (GET vs POST)
- GET: parameters go in the query string; auth goes in headers. Do not send JSON bodies on GET.
- POST: parameters go in the JSON body; auth uses headers and/or body as documented below.
Anti-enumeration (404)
Some org-scoped reads return 404 not-found when the caller is not associated with the org. This hides org existence from unrelated callers. Treat 404 as ambiguous (not found or not associated) and use the self-check flow in /common/troubleshooting.html.
Optimistic concurrency (revision)
For any state-changing operation on an existing record, caller must provide expected_revision matching the current record revision.
- Missing
expected_revision→428 expected-revision-requiredwitherror.details.{current_revision,current_record} - Mismatch →
409 conflictwitherror.details.{provided_revision,current_revision,current_record} - After a successful write, use the response
data.revisionfor your next write.
Create/put
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /mrs/record(inline JSON ≤256 KB)- Req:
{ container, orgcode, cccode?, record_id?, caption?, tags?, doom_at?, content_type: "application/json", payload, idempotency_key?, expected_revision? }(expected_revisionrequired when updating an existingrecord_id) - Res: metadata
{ record_id, status, s3 { bucket,key,version_id,etag }, size_bytes, content_type, orgcode, cccode?, container, caption?, tags?, doom_at?, revision, created_at, updated_at }
- Req:
POST /mrs/record(presign path, >256 KB or non-JSON)- Req:
{ container, orgcode, cccode?, record_id?, caption?, tags?, doom_at?, content_type, content_encoding: "gzip", size_bytes, size_gzip_bytes, content_md5 (hex of gzip body), idempotency_key?, expected_revision? }(expected_revisionrequired when updating an existingrecord_id) - Res:
{ record_id, presign { upload_url, method, headers, expires_at }, content_token, max_size_bytes: 134217728, orgcode, cccode?, container, caption?, tags?, doom_at?, revision }(record status ispending_uploaduntilrecord/completetransitions it toactive)
- Req:
Complete presigned upload
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /mrs/record/complete- Req:
{ container, record_id, orgcode, cccode?, expected_revision, content_token, reported: { size_bytes, size_gzip_bytes, etag, version_id, content_type, content_encoding, content_md5 } }(expected_revisionmust matchrevisionfrom the presign response;content_md5= hex MD5 of gzip payload; size mismatch/etag/MD5 mismatch → 400) - Res: metadata (transitions from
pending_uploadtoactive).
- Req:
Presign error handling (size/gzip/MD5)
Presign and complete validate gzip, sizes, and MD5. Common HTTP 400 tags:
inline-too-large: inline JSON exceeds 256 KB (use presign).gzip-required:content_encodingmust begzipfor presigned uploads.missing-content-md5:content_md5missing.invalid-content-md5:content_md5is not valid hex.missing-size:size_bytes/size_gzip_bytesmissing.too-large: payload exceeds 128 MB cap (bytes or gzip bytes).size-mismatch: reported size does not match presign or object size.md5-mismatch: reported MD5 does not match presign or object MD5.md5-missing: object is missing MD5/ETag when validating completion.etag-mismatch: reported ETag does not match S3 object.invalid-token/upload-expired:content_tokenmismatch or expired.missing-object: uploaded object not found at completion time.type-mismatch/encoding-mismatch: reported content type/encoding does not match presign.
Gzip + MD5 helper snippets
Presign uploads require gzip payloads and content_md5 computed from the gzipped bytes (hex). These helpers return both sizes and the hex MD5 digest.
Downloads:
Read
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"GET /mrs/record/meta- Query:
container,record_id,orgcode,cccode?,include_doomed? - Headers:
x-session-guidorx-api-key - Res: metadata.
- Query:
GET /mrs/record- Query:
container,record_id,orgcode,cccode?,inline_ok? - Headers:
x-session-guidorx-api-key - Res: inline JSON payload if stored inline; else
{ presign { download_url, method, headers, expires_at }, ...metadata }.
- Query:
Examples:
sh
curl -sS -G "https://api.g3nretailstack.com/mrs/record/meta" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "container=demo" \
--data-urlencode "record_id=rec-1"
curl -sS -G "https://api.g3nretailstack.com/mrs/record" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "container=demo" \
--data-urlencode "record_id=rec-1" \
--data-urlencode "inline_ok=true"List
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"GET /mrs/list- Query:
orgcode,cccode?,container?,tag?,status?(defaultactive),include_doomed?,caption_prefix?,record_prefix?,limit?,next_token? - Headers:
x-session-guidorx-api-key - Res:
{ items: [...metadata], next_token? }
- Query:
Examples:
sh
curl -sS -G "https://api.g3nretailstack.com/mrs/list" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "container=demo" \
--data-urlencode "status=active" \
--data-urlencode "limit=8"
curl -sS -G "https://api.g3nretailstack.com/mrs/list" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "tag=INVENTORY" \
--data-urlencode "include_doomed=true"Tags
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /mrs/tag/add/POST /mrs/tag/remove- Req:
{ container, record_id, orgcode, cccode?, tags[], expected_revision } - Res: updated metadata.
- Req:
Doom & TTL
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /mrs/doom- Req:
{ container, record_id, orgcode, cccode?, reason?, expected_revision } - Res: metadata with
status=doomed.
- Req:
POST /mrs/ttl/set- Req:
{ container, record_id, orgcode, cccode?, doom_at, expected_revision } - Res: updated metadata.
- Req:
Head/exists
Canonical curl headers (auth)
bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"GET /mrs/head- Query:
container,record_id,orgcode,cccode? - Headers:
x-session-guidorx-api-key - Res:
{ exists, status?, size_bytes?, size_gzip_bytes?, doom_at? }
- Query:
Example:
sh
curl -sS -G "https://api.g3nretailstack.com/mrs/head" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "orgcode=$ORGCODE" \
--data-urlencode "container=demo" \
--data-urlencode "record_id=rec-1"Operator-only ops (direct Lambda; not API Gateway)
The following maintenance operations are not exposed on the tenant-facing API surface. Calling these paths on the API Gateway base returns 404 not-found.
mrs_reconcile(Lambda)- Req:
{ orgcode, container, mode? ("report-only"|"repair"), max_items? (default 25), next_token? } - Res
data:{ mode, checked, drifts: [{ record_id, reason }], next_token? }(repairs when mode=repair)
- Req:
mrs_tag_scrub(Lambda)- Req:
{ orgcode, container, limit? (default 25), next_token? } - Res
data:{ scrubbed, processed, next_token? }
- Req:
mrs_cleanup(Lambda)- Req:
{ orgcode, container, limit? (default 50), mode? ("dry-run"|"execute", default "dry-run") }— retains only items older than the cleanup retention (default 90d viaMRS_CLEANUP_RETENTION_DAYS); dry-run reports counts without deleting. - Res
data:{ mode, processed, tags_scrubbed, rollups_deleted, next_token?, cutoff }
- Req:
Example (reconcile report-only):
json
{ "orgcode": "ORGCODE", "container": "demo", "mode": "report-only", "max_items": 10 }Response (shape):
json
{ "success": true, "data": { "mode": "report-only", "checked": 10, "drifts": [{ "record_id": "rec-1", "reason": "missing-object" }], "next_token": "..." }, "build": { "...": "..." }, "stats": { "...": "..." } }Envelope
Responses: { success, data?, error?, build, stats } with stats including call, service mrs, request_id, timestamp_utc, latency_ms, bandwidth_in_bytes/out_bytes, ddb_calls, other_service_calls, and passthrough actor/session/user/org/cc when provided. Content is never emitted; only metadata/presigned URLs.
CLI mapping
- API-backed:
g3n mrs put(auto inline vs presign; presign path auto-uploads + completes),mrs get,mrs get-meta,mrs list,mrs tag-add,mrs tag-remove,mrs doom,mrs ttl-set,mrs head. Requires--session-guidand org scope. - Operator-only direct Lambda:
g3n mrs reconcile,g3n mrs tag-scrub,g3n mrs cleanup. Requires--profile(IAM) plus--orgcode, and is not tied tosession_guid. - Inline example:
g3n mrs put --profile g3nretailstack --session-guid $SESSION --orgcode $ORG --container demo --inline-payload-file payload.json --record-id rec1 --content-type application/json - Presign example:
g3n mrs put --profile g3nretailstack --session-guid $SESSION --orgcode $ORG --container demo --file path/to/blob.bin --content-type application/octet-stream --record-id rec2(CLI gzips, computes hex MD5 of the gzip, enforces 128 MB cap, and completes automatically) - List include_doomed example:
g3n mrs list --profile g3nretailstack --session-guid $SESSION --orgcode $ORG --status all --container demo(returns active + doomed; use--next-tokento page) - Tag add example:
g3n mrs tag-add --profile g3nretailstack --session-guid $SESSION --orgcode $ORG --container demo --record-id rec1 --tags foo --expected-revision rev-123 - Reconcile example (operator-only):
g3n mrs reconcile --profile g3nretailstack --orgcode $ORG --container demo --mode report-only --max-items 10(reports drifts without repairing; swap--mode repairto heal; use--next-tokento continue) - Error example (missing gzip on >256 KB): HTTP 400 with
error.major.tag="gzip-required"andsuccess=falsein the envelope.
Error envelope example (canonical)
json
{
"success": false,
"error": {
"error_code": "mrs.conflict_revision",
"http_status": 409,
"retryable": false,
"request_id": "req-123",
"trace_id": "trace-abc",
"major": { "tag": "conflict", "message": { "en_US": "Expected revision does not match the current record." } },
"details": { "expected_revision": "3", "current_revision": "4" },
"conflict_snapshot": { "revision": 4 }
},
"build": { "...": "..." },
"stats": { "call": "example", "service": "mrs", "timestamp_utc": "2026-01-21T00:00:00Z", "request_id": "req-123" }
}The error block includes the following optional fields (matching the ICS/PPM/SCM canonical pattern):
error_code(string, optional) — machine-readable error code (e.g.mrs.conflict_revision).http_status(number, optional) — HTTP status code.retryable(boolean, optional) — whether the caller should retry.request_id(string, optional) — correlation request ID.trace_id(string, optional) — distributed tracing ID.
These fields are present when available; callers should not depend on their presence.
Role requirements (by endpoint family)
- Read/list:
mrs_reader(or owner). - Write/create/update:
mrs_writer(or owner). - Maintenance (reconcile/tag-scrub/cleanup): operator-only direct Lambdas.
Idempotency & retries
- Use
idempotency_keyon create/update when safe retries are needed. record/completeis idempotent whencontent_tokenandexpected_revisionmatch.
Common pitfalls
- Presigned uploads must be gzip and
content_md5must be computed on gzip bytes. containeris lowercase and validated; tags are uppercased.orgcodeis required even on read/list.
Examples (core families)
Inline JSON create
json
{ "orgcode": "ORGCODE", "container": "notes", "content_type": "application/json", "payload": { "hello": "world" } }Response (shape):
json
{ "success": true, "data": { "record_id": "rec-1", "status": "active" }, "build": { "...": "..." }, "stats": { "...": "..." } }