Skip to content

Surfaces

Interactive API Explorer: Explorer

API Gateway base: https://api.g3nretailstack.com/ofm (custom domain). Invitations are operator-only direct Lambdas (create/get/list/reject/doom + resolveInvitation) and are not exposed via API Gateway; everything else is under /ofm/* on API Gateway. Canonical auth: headers x-session-guid (human USM session) or x-api-key (service-account). Body session_guid / api_key are accepted for compatibility. Service accounts are org-bound and role-limited; owner-only OFM mutations require the service-account role owner. Responses share { success, data?, error?, build, stats? } with build ID + UTC timestamp in the footer. Errors include tags/messages; payloads redact sensitive fields. Pagination: default limit 8, clamp 1–256, opaque next_token JSON cursors.

Surface Types (explicit)

API Gateway

  • Status: Available.
  • Base: https://api.g3nretailstack.com/ofm
  • Notes: All non-invitation endpoints are under API Gateway; invitations are operator-only direct Lambda.

Direct Lambda

  • Status: Available (operator-only).
  • Notes: Invitations and org verification flows are direct Lambda (IAM-gated).

CLI

  • Status: Available.
  • Command: g3n ofm ... (API Gateway; admin direct Lambdas for operator support).
  • Notes: See cli/README.md for operator-only commands.

MCP

  • Status: Available.
  • Canonical protocol: https://mcp.g3nretailstack.com/ofm/PROTOCOL.md
  • Mirror: https://doc.g3nretailstack.com/ofm/PROTOCOL.md

Auth + tenancy

  • Auth headers: x-session-guid (user session) or x-api-key (service account). Header auth is canonical; body auth accepted for compatibility.
  • Org identity is always in the JSON body (org_guid, orgcode, logical_guid depending on endpoint).
  • Service accounts are org-bound; owner-only mutations require the service-account role owner.
  • Owner-management ops (/owner/primary/set, /owner/secondary/add|remove, /owner/state/set) require the active primary owner; other owner-only ops allow any active owner.
  • Non-associated callers receive 404 not-found (anti-enumeration).
  • Suspended members are treated as non-associated (404 not-found); suspended owners are associated but are not owners (403 not-owner on owner-only ops).
  • Service-account reads require a view role (ofm_view/pvv/pma/vca/pmc_view/pmc_publish) or owner; otherwise 403 forbidden-role.
  • Facility-scoped checks require explicit logical assignments; missing assignment returns 403 forbidden-facility.
  • Frozen orgs block access: 403 org-access-blocked.
  • Tenant writes require org.status=verified: otherwise 409 org-write-blocked.

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.

Operator-only seams

  • Invitations (create/get/list/reject/doom + resolveInvitation) are IAM direct Lambdas, not API Gateway.
  • Org verification (unverified → verified) is operator-only. Until verified, org writes across services return 403 org-write-blocked.

Request builder (API Gateway)

Method posture: OFM uses POST for reads/lists/resolves and writes.

Headers (canonical)

bash
-H "content-type: application/json"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"

Body placement

  • Org identity is always in the JSON body (org_guid, orgcode, logical_guid depending on the endpoint).
  • Body session_guid / api_key is accepted for compatibility, but headers are canonical.

Template

bash
curl -sS -X POST "https://api.g3nretailstack.com/ofm/org/get" \
  -H "content-type: application/json" \
  -H "x-session-guid: $SESSION_GUID" \
  -d '{"org_guid":"ORG_GUID"}'

Why POST for reads?

OFM uses POST for reads/lists/resolves to keep auth out of URLs, accept structured JSON bodies (org GUIDs, codes, filters), and keep the contract uniform. Treat read endpoints as POST-only (no GET).

Retry guidance (endpoint class)

  • Read-ish POSTs (get/list/resolve) are safe to retry with identical inputs.
  • Write-ish POSTs (create/update/status/set/assign) require expected_revision and should not be retried unless the mutation is idempotent for your input.

Optimistic concurrency (revision)

Most OFM records include a per-record revision GUID. For any state-changing operation that updates an existing record, the caller must include expected_revision (the revision they last read).

  • Missing expected_revision → HTTP 428 (expected-revision-required)
  • Revision mismatch → HTTP 409 (conflict)
  • Failure payload includes error.details.{ provided_revision?, current_revision, current_record }
  • On mismatch, OFM emits a best-effort revisionConflict event.

Pagination (API + CLI)

  • Default limit is 8 (clamped 1–256). next_token is opaque JSON.
  • Echo next_token verbatim to continue listing.

Example (API list with cursor):

json
{ "org_guid": "ORG_GUID", "limit": 2 }

Response (shape):

json
{ "success": true, "data": { "items": [{ "...": "..." }], "next_token": { "PK": "...", "SK": "..." } }, "build": { "...": "..." }, "stats": { "...": "..." } }

Example (CLI cursor):

g3n ofm org-list --org-guid ORG_GUID --limit 2 --session-guid $SESSION_GUID --profile g3nretailstack
# copy next_token from response
g3n ofm org-list --org-guid ORG_GUID --next-token '{"PK":"...","SK":"..."}' --session-guid $SESSION_GUID --profile g3nretailstack

Terminal doom (doomed)

For any record in status/state doomed, OFM rejects further mutations (no transitions out; updates and association changes are blocked) with HTTP 409 (invalid-state).

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. Notes:

  • Owner-only endpoints return 403 not-owner when the caller is associated but not an owner.
  • Service-account reads without a view role return 403 forbidden-role (never 404).

Auth/anti-enumeration regression matrix (org-scoped reads/lists/resolvers)

This matrix covers read/list/resolve endpoints that require org association, owner privilege, or facility grants. Use it to validate anti-enumeration and role gating.

Access gateEndpointsNon-associated sessionSuspended memberAssociated but missing gateService account wrong orgService account missing role
Org association (member/owner)org/get, resolve-orgcode, member/resolve404404200404403 (forbidden-role)
Owner-onlyowner/list, member/list, cost-centre/get, cost-centre/list, resolve-facility, resolve-cost-centre, `facility/physical/getlist, facility/legal/getlist, facility/logical/getlist, sales-channel/list` (org-wide)404404
Facility grant (logical) or ownerzone/get, zone/list, resolve-zone, team/list (when logical_guid provided), team/get (logical teams), team/members (logical teams), sales-channel/get, sales-channel/list (when logical_guid provided), sales-channel/resolve404404403 (forbidden-facility)404403 (forbidden-role/forbidden-facility)
Self or ownermember/assignments (self), team/by-member (self)404404403 (not-owner for non-self)404403 (forbidden-role or not-owner)

Notes:

  • org/list is session-scoped (lists orgs tied to the principal) and returns 200 with an empty list when the principal has no orgs; it does not 404 for anti-enumeration.
  • Team endpoints are conditional: org-wide teams are owner-only; logical-scoped teams require facility grants.
  • Sales-channel list is conditional: org-wide list requires owner; logical-scoped list requires facility grants.
  • Service accounts must be org-bound; wrong org is always 404 not-found (anti-enumeration).
  • Owner-management endpoints (owner/primary/set, owner/secondary/add|remove, owner/state/set) require the active primary owner; other owners receive 403 not-owner.
  • member/resolve requires a human session; service-account API keys are rejected (403 invalid-session).

Public API (API Gateway)

Canonical curl headers (auth)

bash
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"

Org identity lives in the JSON body (org_guid, orgcode, logical_guid) per endpoint; auth is header-only (body auth accepted for compatibility). Method posture: OFM uses POST for reads/lists/resolves to accept structured JSON bodies. Treat POST as canonical; do not send JSON bodies on GET. See /common/http-methods.html.

Organizations

  • Create POST /org/create{ orgcode, invitation_code, user_guid, session_guid, caption?, timezone?, fiscal_calendar?, reason? }{ org_guid, orgcode, status, invitation {...}, owners {...}, cost_centre { cc_guid, cccode }, timezone?, fiscal_calendar? }. Requires session; invitation must be valid. Errors: invalid-code, invitation-consumed, invitation-expired, uniqueness-conflict, code-generation-exhausted, validation-error.
  • Get POST /org/get — POST read (no GET). { org_guid?, orgcode? } → org snapshot including cost_centre_guid + cost_centre { cc_guid, cccode } for the auto-created master cost centre (and search_plane when configured). Errors: not-found.
  • List POST /org/list — POST read (no GET). filters: status? (includes frozen), pagination limit/next_token. Errors: validation-error.
  • Update POST /org/update{ org_guid, expected_revision, caption?, timezone?, fiscal_calendar?, search_plane?, reason? } (set timezone/fiscal_calendar/search_plane to null to clear). Errors: not-owner, not-found, validation-error.
  • Status set POST /org/status/set{ org_guid, expected_revision, status, reason?, reason_code?, actor?, session_guid? } (FSM: unverified→verified/parked/suspended→frozen→doomed). API owners can only toggle verified↔parked; frozen is operator-only via direct Lambda. Errors: invalid-fsm-transition, not-owner, not-found.

Example (org create):

json
{
  "orgcode": "ACME",
  "invitation_code": "INV123",
  "user_guid": "u1",
  "session_guid": "s1",
  "caption": "Acme HQ",
  "timezone": "America/Los_Angeles",
  "fiscal_calendar": { "code": "retail-454", "start_month": 2, "start_day": 1, "week_start": "sun" }
}

Cost centres

  • Create POST /cost-centre/create{ org_guid, caption?, reason?, actor?, session_guid? }{ cc_guid, cccode, status }. Errors: not-owner, validation-error, code-generation-exhausted.
  • Get POST /cost-centre/get — POST read (no GET). { cc_guid?, cccode?, org_guid? }. Errors: not-found, validation-error.
  • List POST /cost-centre/list — POST read (no GET). { org_guid, status?, limit?, next_token?, actor?, session_guid? }.
  • Update POST /cost-centre/update{ org_guid, cc_guid, expected_revision, caption?, reason?, actor?, session_guid? }.
  • Status set POST /cost-centre/status/set{ org_guid, cc_guid, expected_revision, status, reason?, actor?, session_guid? } (active↔suspended→doomed).
    • Use cases: org create auto-seeds a master cost centre, add CCs per division, suspend or doom retired CCs.

Facilities

  • Physical create POST /facility/physical/create — code unique per org. Create includes address { street, city, region, country }, phone, optional fax/email/primary_contact.
  • Physical get POST /facility/physical/get — POST read (no GET). get a physical facility by org + guid or code.
  • Physical list POST /facility/physical/list — POST read (no GET). list physical facilities for an org.

Role requirements (by endpoint family)

  • Org governance (org status): owner only. Owner management (primary/secondary/state) requires the active primary owner.
  • Membership (invites, member update, assignments): owner or ofm_member_admin.
  • Teams: owner or ofm_team_admin.
  • Sales channels: owner or ofm_channel_admin (draft/config); activation/dooming remains owner-only.
  • Reads/resolves/lists: any associated member; role requirements per family above.

Idempotency & retries

  • Updates require expected_revision. On 409/428, re-read and retry.
  • Create endpoints are not idempotent unless a caller-provided code is used and uniqueness is enforced.

Common pitfalls

  • OFM is POST-only (no GET).
  • Org identity is in the body, not headers.
  • Org must be verified before tenant writes; operator verification is required.
  • Suspended members behave like non-associated for org-scoped reads (404 not-found).

Examples (core families)

Member resolve (anti-enumeration self-check)

json
{ "orgcode": "ORGCODE", "logical_guid": "LOGICAL_GUID" }

Response (shape):

json
{ "success": true, "data": { "member": { "user_guid": "U1", "roles": ["owner"] } }, "build": { "...": "..." }, "stats": { "...": "..." } }

Sales channel create

json
{ "org_guid": "ORG_GUID", "logical_guid": "LOGICAL_GUID", "channel_code": "WEB", "market_code": "US", "locale_codes": ["en-US"], "default_locale_code": "en-US" }

Response (shape):

json
{ "success": true, "data": { "channel_guid": "CH_GUID" }, "build": { "...": "..." }, "stats": { "...": "..." } }

Member assign logical (facility delegation)

json
{ "org_guid": "ORG_GUID", "user_guid": "USER_GUID", "logical_guid": "LOGICAL_GUID", "role_profile_id": "inventory_clerk", "grants": ["facility:zones_write"], "effective_from": "2026-02-01T00:00:00Z" }

Response (shape):

json
{ "success": true, "data": { "user_guid": "USER_GUID", "logical_guid": "LOGICAL_GUID", "state": "active" }, "build": { "...": "..." }, "stats": { "...": "..." } }

Team list (paginated)

json
{ "org_guid": "ORG_GUID", "limit": 3, "next_token": null }

Response (shape):

json
{ "success": true, "data": { "items": [{ "team_guid": "TEAM_GUID" }], "next_token": { "PK": "...", "SK": "..." } }, "build": { "...": "..." }, "stats": { "...": "..." } }

Anti-enumeration (non-associated)

json
{ "org_guid": "ORG_GUID" }

Response (shape):

json
{ "success": false, "error": { "major": { "tag": "not-found", "message": { "en_US": "Organization not found" } } }, "build": { "...": "..." }, "stats": { "...": "..." } }

CLI samples (contract parity)

Org lifecycle

g3n ofm org-create --orgcode ACMECORP --invitation-code ABC-DEF-1234 --user-guid user-1 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm org-status-set --org-guid org-123 --status verified --expected-revision rev-org-1 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm org-update --org-guid org-123 --caption "ACME Retail" --expected-revision rev-org-2 --session-guid $SESSION_GUID --profile g3nretailstack

Facilities + zones

g3n ofm physical-create --org-guid org-123 --code PF-1 --street "123 Main" --city Gotham --region NY --country US --phone "+1-555-1234" --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm legal-create --org-guid org-123 --code LG-1 --caption "ACME Legal" --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm logical-create --org-guid org-123 --code LQ-1 --physical-guid pf-1 --legal-guid lg-1 --cost-centre-guid cc-1 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm zone-create --org-guid org-123 --logical-guid lq-1 --parent-zone-guid ROOT --code A1 --session-guid $SESSION_GUID --profile g3nretailstack

Members + teams

g3n ofm member-invite-create --org-guid org-123 --invitee-user-guid user-2 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm member-invite-accept --code ABC-DEF-1234 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm member-assign-logical --org-guid org-123 --user-guid user-2 --logical-guid lq-1 --role-profile-id inventory_clerk --grants '["facility:zones_write"]' --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm member-resolve --org-guid org-123 --logical-guid lq-1 --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm team-create --org-guid org-123 --code OPS --caption "Ops Team" --session-guid $SESSION_GUID --profile g3nretailstack
g3n ofm team-member-add --org-guid org-123 --team-guid team-1 --user-guid user-2 --session-guid $SESSION_GUID --profile g3nretailstack
  • Physical update POST /facility/physical/update — update physical facility fields with expected_revision.
  • Physical status POST /facility/physical/status — status: active↔inactive→doomed.
  • Legal create POST /facility/legal/create — code unique per org.
  • Legal get POST /facility/legal/get — POST read (no GET). get a legal facility by org + guid or code.
  • Legal list POST /facility/legal/list — POST read (no GET). list legal facilities for an org.
  • Legal update POST /facility/legal/update — update legal facility fields with expected_revision.
  • Legal status POST /facility/legal/status — status: active↔inactive→doomed.
  • Logical create POST /facility/logical/create — code unique per org; requires physical_guid + legal_guid (same org); optional cost_centre_guid.
  • Logical get POST /facility/logical/get — POST read (no GET). get a logical facility by org + guid or code.
  • Logical list POST /facility/logical/list — POST read (no GET). list logical facilities for an org.
  • Logical update POST /facility/logical/update — update logical facility fields with expected_revision.
  • Logical status POST /facility/logical/status — status: active↔inactive→doomed.
  • Use cases: model physical/legal entities, attach logical facilities for operations, tie to cost centres.
  • Errors: invalid-code, not-owner, not-found, validation-error, uniqueness-conflict, invalid-fsm-transition.

Example (logical create):

json
{
  "org_guid": "org1",
  "physical_guid": "phys1",
  "legal_guid": "leg1",
  "code": "LOG1",
  "caption": "Online DC",
  "cost_centre_guid": "cc1",
  "actor": "ops-owner",
  "session_guid": "s1"
}

Zones

  • Create POST /zone/create{ org_guid, logical_guid, parent_zone_guid, code, caption?, reason?, actor?, session_guid? }; ROOT is seeded on facility/logical/create (and zone/create also ensures it exists for legacy logicals); depth ≤32; code unique per logical; code ROOT is reserved.
  • Get POST /zone/get — POST read (no GET). { org_guid, logical_guid, zone_guid?, code? }.
  • List POST /zone/list — POST read (no GET). { org_guid, logical_guid, parent_zone_guid?, limit?, next_token?, actor?, session_guid? }.
  • Status POST /zone/status{ org_guid, logical_guid, zone_guid, expected_revision, status, reason?, actor?, session_guid? } (active↔inactive→doomed).
  • Response notes: zone records include zone_guid in list/get responses (derived from storage keys for legacy rows).
  • Use cases: create hierarchy for routing/fulfillment; suspend or doom retired branches. Errors: invalid-depth, invalid-code, invalid-fsm-transition, not-owner, not-found.

Sales channels (declarations)

  • Create (draft) POST /sales-channel/create{ org_guid, logical_guid, channel_code, market_code, locale_codes, default_locale_code?, external_ids?, caption?, reason?, actor?, session_guid? }{ channel_guid, status: "draft", revision } (locale_codes required; no locale defaulting when omitted).
  • Get POST /sales-channel/get — POST read (no GET). { org_guid, channel_guid, actor?, session_guid? } → sales channel record (incl. revision, status, locale fields, and any config pointer metadata). Requires facility grant for the channel’s logical_guid (or owner).
  • List POST /sales-channel/list — POST read (no GET). { org_guid, logical_guid?, status?, channel_code?, market_code?, limit?, next_token?, actor?, session_guid? }{ sales_channels: [...], next_token? }. Requires owner for org-wide lists; when logical_guid is provided, requires facility grant for that logical (or owner).
  • Resolve (by external id) POST /sales-channel/resolve — POST read (no GET). { org_guid, external_id_kind: "shop_domain"|"external_store_id", external_id_descriptor?, external_id_value, actor?, session_guid? } → sales channel record. Requires facility grant for the channel’s logical_guid (or owner).
    • If an active binding exists for the external id, it is returned.
    • If no active binding exists: returns the unique match when exactly one non-doomed channel record matches; otherwise returns 409 ambiguous.
  • Update POST /sales-channel/update{ org_guid, channel_guid, expected_revision, caption?, logical_guid?, channel_code?, market_code?, locale_codes?, default_locale_code?, external_ids?, reason?, actor?, session_guid? }.
    • caption is editable in draft|active|inactive.
    • logical_guid, channel_code, market_code, and locale fields are editable only while status=draft (immutable after leaving draft).
    • doomed is terminal: no updates allowed.
  • Status set POST /sales-channel/status{ org_guid, channel_guid, status, expected_revision, reason?, actor?, session_guid? } with FSM:
    • draft -> active|doomed
    • active -> inactive
    • inactive -> active|doomed
    • active -> doomed is rejected (must go active -> inactive -> doomed).
    • draft -> active revalidates channel_code registry, strict market_code, locale canonicalization/region matching, and that logical_guid exists (not doomed).
    • draft|inactive -> active also enforces uniqueness of the active external-id binding; conflicts return 409 external-id-active-in-use.
  • Config presign POST /sales-channel/config/presign{ org_guid, channel_guid, expected_revision, session_guid }{ presign { upload_url, method, headers, expires_at }, config_s3_bucket, config_s3_key }.
  • Config complete POST /sales-channel/config/complete{ org_guid, channel_guid, expected_revision, config_s3_key, reason?, actor?, session_guid? }{ config { config_s3_bucket, config_s3_key, config_s3_version_id, config_etag, config_size_bytes }, revision }.
    • Config content is opaque and is never emitted inline in OFM events/logs; pointer-only.

Owners

  • List POST /owner/list — POST read (no GET). { org_guid, limit?, next_token?, actor?, session_guid? }.
  • Primary set POST /owner/primary/set{ org_guid, user_guid, expected_revision, reason?, actor?, session_guid? } (current primary required; expected_revision is the org record revision).
  • Secondary add POST /owner/secondary/add{ org_guid, user_guid, expected_revision?, reason?, actor?, session_guid? } (expected_revision required when updating an existing owner record).
  • Secondary remove POST /owner/secondary/remove{ org_guid, user_guid, expected_revision?, reason?, actor?, session_guid? } (expected_revision required when updating an existing owner record).
  • State set POST /owner/state/set{ org_guid, user_guid, expected_revision, state, reason?, actor?, session_guid? } (active/suspended/doomed).
  • Use cases: manage ownership for admin actions; elevate/demote secondary owners. Errors: not-owner, invalid-fsm-transition, not-found.
  • Response notes: owner list items include user_guid.

Members

  • Invite create POST /member/invite/create{ org_guid, caption?, invitee_user_guid, expires_at_utc?, role_profile_id?, role_version?, grants?, effective_from?, effective_to?, notes?, actor?, session_guid? }{ code, invite_guid, status: "active", revision }.
    • Owner-only. Returns an invite code to share out-of-band.
  • Invite accept POST /member/invite/accept{ code, session_guid }{ org_guid, user_guid, state, revision }.
    • Bound invites: accept requires the session user to match invitee_user_guid (mismatch returns 404 not-found).
    • Creates the org member record (doomed members cannot be re-added).
  • Invite list POST /member/invite/list — POST read (no GET). { org_guid, status?, limit?, next_token?, actor?, session_guid? }{ invites[], next_token? }.
  • Invite revoke POST /member/invite/revoke{ org_guid, invite_guid?|code?, expected_revision, reason?, actor?, session_guid? }{ invite_guid, status: "doomed", revision }.
  • State set POST /member/state/set{ org_guid, user_guid, expected_revision, state, actor?, session_guid? } (active/suspended/doomed; doomed is terminal).
  • Assign/detach logical POST /member/assign-logical / POST /member/detach-logical{ org_guid, user_guid, logical_guid, expected_revision?, ... } (expected_revision required for detach).
  • List POST /member/list — POST read (no GET). { org_guid, state?, limit?, next_token?, actor?, session_guid? }.
  • Assignments POST /member/assignments — POST read (no GET). { org_guid, user_guid?, limit?, next_token?, actor?, session_guid? } (self-mode when user_guid omitted).
  • Resolve POST /member/resolve — POST read (no GET). { org_guid|orgcode, session_guid, logical_guid? }{ is_owner, roles[], org_status, member_state, ... } (enforces effective_from/effective_to at resolve time; when logical_guid is provided, returns facility-scoped logical_access + logical_roles and validates the logical exists in-org).
  • Use cases: onboard staff, reassign to logical facilities, suspend or doom access. Errors: duplicate-member, invalid-fsm-transition, not-owner, not-found, validation-error.

Service accounts (facility delegation)

Use these endpoints to delegate facility-scoped access for service-account API keys (created in USM). These assignments are only used by facility-scoped checks (e.g., zones, logical teams, sales channels). For human users, use member/assign-logical.

  • Assign logical POST /service-account/assign-logical{ org_guid, service_account_guid, logical_guid, state?, role_profile_id?, role_version?, grants?, effective_from?, effective_to?, notes?, actor?, session_guid? }{ org_guid, service_account_guid, logical_guid, state, revision }.
    • Owner-only. effective_from/effective_to are enforced by facility-grant checks.
  • Detach logical POST /service-account/detach-logical{ org_guid, service_account_guid, logical_guid, expected_revision, actor?, session_guid? }{ detached: true }.
    • Owner-only. Requires optimistic concurrency (expected_revision).
  • Assignments POST /service-account/assignments — POST read (no GET). { org_guid, service_account_guid, limit?, next_token? }{ assignments[], next_token? }.
    • Owner-only. Lists logical assignments for that service account.

Teams

  • Create POST /team/create{ org_guid, logical_guid?, code?, caption?, reason?, actor?, session_guid? } (logical_guid scopes the team to a logical facility; teams remain non-authoritative).
  • Get POST /team/get — POST read (no GET). { org_guid, team_guid?, code?, actor?, session_guid? }.
  • List POST /team/list — POST read (no GET). { org_guid, status?, logical_guid?, limit?, next_token?, actor?, session_guid? }.
  • Update POST /team/update{ org_guid, team_guid, expected_revision, caption?, code?, logical_guid?, actor?, session_guid? } (logical_guid=null clears).
  • Status POST /team/status{ org_guid, team_guid, expected_revision, status, actor?, session_guid? } (active/suspended/doomed).
  • Member add POST /team/member/add{ org_guid, team_guid, user_guid, expected_revision?, actor?, session_guid? }.
  • Member remove POST /team/member/remove{ org_guid, team_guid, user_guid, expected_revision?, actor?, session_guid? } (expected_revision required for remove).
  • Members POST /team/members — POST read (no GET). { org_guid, team_guid, limit?, next_token?, actor?, session_guid? }.
  • By-member POST /team/by-member — POST read (no GET). { org_guid, user_guid?, limit?, next_token?, actor?, session_guid? } (self lookups allowed; querying another user requires owner). Org scope required.
  • Use cases: group members, track assignments, facility-scoped work routing (via logical_guid), audit team membership by user. Errors: not-owner, invalid-fsm-transition, duplicate-member, not-found, validation-error.
  • Response notes: team list/get records include team_guid (derived from storage keys for legacy rows).

Resolvers

  • POST /resolve/orgcode — POST read (no GET). { orgcode }{ org_guid }.
  • POST /resolve/facility — POST read (no GET). { org_guid, kind: "physical"|"legal"|"logical", code }{ guid }. Owner-only.
  • POST /resolve/zone — POST read (no GET). { logical_guid, code }{ zone_guid }. Requires facility grant for the logical (or owner).
  • POST /resolve/cost-centre — POST read (no GET). { cccode }{ cc_guid }. Owner-only.
  • Use cases: translate human-friendly codes to GUIDs for follow-on calls. Errors: not-found, validation-error.

Direct Lambda actions

  • invitationCreate{ caption?, expires_at_utc?, referral_code?, schedule?, reason? }{ code, status }. Use to mint invites without auth. Errors: invalid-body, invalid-input, code-generation-exhausted, internal-error.
  • invitationGet{ code } → invitation snapshot.
  • invitationList{ status?, limit?, next_token? }{ invitations[], next_token? }.
  • invitationReject{ code, expected_revision, reason? }{ code, status: "rejected" }.
  • invitationDoom{ code, expected_revision, reason? }{ code, status: "doomed" }. Use to retire compromised/unused invites. Errors: invalid-code, not-found, invalid-fsm-transition.
  • resolveInvitation{ code }{ invitation_guid }.
  • orgStatusSet{ org_guid, status, expected_revision, reason? }{ org_guid, status, revision } (operator-only direct Lambda; function ofm_orgstatusset; supports frozen for UTL offboarding freeze).
  • ownerPrimarySetAdmin{ org_guid, user_guid, expected_revision, reason? }{ org_guid, user_guid, primary_owner: true, revision } (operator-only direct Lambda; function ofm_ownerprimaryset_admin).
  • ownerStateSetAdmin{ org_guid, user_guid, state, expected_revision, reason? }{ org_guid, user_guid, state, revision } (operator-only direct Lambda; function ofm_ownerstateset_admin).
  • memberStateSetAdmin{ org_guid, user_guid, state, expected_revision, reason? }{ org_guid, user_guid, state, revision } (operator-only direct Lambda; function ofm_memberstateset_admin).

CLI

g3n ofm wraps the same API/Lambda surfaces. Direct Lambda commands require --profile; API Gateway commands require session_guid and accept --base-url; --next-token accepts JSON cursors.

Common commands:

  • g3n ofm invitation-create --caption "Q1 invite" --profile g3nretailstack
  • g3n ofm post /org/create --body '{...}' --profile g3nretailstack (generic), or dedicated commands per surface as added.
  • g3n ofm post /zone/create --body '{ "org_guid": "...", "logical_guid": "...", "parent_zone_guid": "ROOT", "code": "A1" }' --profile g3nretailstack
  • g3n ofm post /team/by-member --body '{ "org_guid": "...", "user_guid": "...", "limit": 8 }' --profile g3nretailstack
  • Pagination (CLI cursor): g3n ofm org-list --org-guid ORG_GUID --limit 2 --session-guid $SESSION --profile g3nretailstack → copy JSON next_token, then g3n ofm org-list --org-guid ORG_GUID --next-token '{"PK":"...","SK":"..."}' --session-guid $SESSION --profile g3nretailstack

Best practices: include actor/reason for audit clarity, echo next_token exactly for pagination, and use resolvers to translate codes before making write operations.

Error envelope example (canonical)

json
{
  "success": false,
  "error": {
    "error_code": "ofm.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": "ofm", "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. ofm.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.