Skip to content

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_uploadactivedoomed (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.md for 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) or x-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.

RouteRequired roles
GET /record, GET /record/meta, GET /list, GET /headmrs_reader or mrs_writer
POST /tag/add, POST /tag/removemrs_writer
POST /record (put), POST /record/complete, POST /doom, POST /ttl/setmrs_writer

Identifier policy

  • Direct get/update/status calls require GUID/ID fields (*_id or legacy *_guid where that is the canonical field name). Code-based lookups are resolve/search only.
  • Responses never include both *_id and *_guid for 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_revision428 expected-revision-required with error.details.{current_revision,current_record}
  • Mismatch → 409 conflict with error.details.{provided_revision,current_revision,current_record}
  • After a successful write, use the response data.revision for 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_revision required when updating an existing record_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 }
  • 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_revision required when updating an existing record_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 is pending_upload until record/complete transitions it to active)

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_revision must match revision from the presign response; content_md5 = hex MD5 of gzip payload; size mismatch/etag/MD5 mismatch → 400)
    • Res: metadata (transitions from pending_upload to active).

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_encoding must be gzip for presigned uploads.
  • missing-content-md5: content_md5 missing.
  • invalid-content-md5: content_md5 is not valid hex.
  • missing-size: size_bytes / size_gzip_bytes missing.
  • 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_token mismatch 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-guid or x-api-key
    • Res: metadata.
  • GET /mrs/record
    • Query: container, record_id, orgcode, cccode?, inline_ok?
    • Headers: x-session-guid or x-api-key
    • Res: inline JSON payload if stored inline; else { presign { download_url, method, headers, expires_at }, ...metadata }.

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? (default active), include_doomed?, caption_prefix?, record_prefix?, limit?, next_token?
    • Headers: x-session-guid or x-api-key
    • Res: { items: [...metadata], next_token? }

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.

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.
  • POST /mrs/ttl/set
    • Req: { container, record_id, orgcode, cccode?, doom_at, expected_revision }
    • Res: updated metadata.

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-guid or x-api-key
    • Res: { exists, status?, size_bytes?, size_gzip_bytes?, doom_at? }

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)
  • mrs_tag_scrub (Lambda)
    • Req: { orgcode, container, limit? (default 25), next_token? }
    • Res data: { scrubbed, processed, next_token? }
  • 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 via MRS_CLEANUP_RETENTION_DAYS); dry-run reports counts without deleting.
    • Res data: { mode, processed, tags_scrubbed, rollups_deleted, next_token?, cutoff }

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-guid and 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 to session_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-token to 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 repair to heal; use --next-token to continue)
  • Error example (missing gzip on >256 KB): HTTP 400 with error.major.tag="gzip-required" and success=false in 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_key on create/update when safe retries are needed.
  • record/complete is idempotent when content_token and expected_revision match.

Common pitfalls

  • Presigned uploads must be gzip and content_md5 must be computed on gzip bytes.
  • container is lowercase and validated; tags are uppercased.
  • orgcode is 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": { "...": "..." } }