Appearance
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.mdfor 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) orx-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_guiddepending 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-owneron owner-only ops). - Service-account reads require a view role (
ofm_view/pvv/pma/vca/pmc_view/pmc_publish) orowner; otherwise403 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: otherwise409 org-write-blocked.
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.
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 403org-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_guiddepending on the endpoint). - Body
session_guid/api_keyis 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_revisionand 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→ HTTP428(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
revisionConflictevent.
Pagination (API + CLI)
- Default
limitis 8 (clamped 1–256).next_tokenis opaque JSON. - Echo
next_tokenverbatim 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 g3nretailstackTerminal 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-ownerwhen 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 gate | Endpoints | Non-associated session | Suspended member | Associated but missing gate | Service account wrong org | Service account missing role |
|---|---|---|---|---|---|---|
| Org association (member/owner) | org/get, resolve-orgcode, member/resolve | 404 | 404 | 200 | 404 | 403 (forbidden-role) |
| Owner-only | owner/list, member/list, cost-centre/get, cost-centre/list, resolve-facility, resolve-cost-centre, `facility/physical/get | list, facility/legal/get | list, facility/logical/get | list, sales-channel/list` (org-wide) | 404 | 404 |
| Facility grant (logical) or owner | zone/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/resolve | 404 | 404 | 403 (forbidden-facility) | 404 | 403 (forbidden-role/forbidden-facility) |
| Self or owner | member/assignments (self), team/by-member (self) | 404 | 404 | 403 (not-owner for non-self) | 404 | 403 (forbidden-role or not-owner) |
Notes:
org/listis session-scoped (lists orgs tied to the principal) and returns200with 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 receive403 not-owner. member/resolverequires 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 includingcost_centre_guid+cost_centre { cc_guid, cccode }for the auto-created master cost centre (andsearch_planewhen configured). Errors:not-found. - List
POST /org/list— POST read (no GET). filters:status?(includesfrozen), paginationlimit/next_token. Errors:validation-error. - Update
POST /org/update—{ org_guid, expected_revision, caption?, timezone?, fiscal_calendar?, search_plane?, reason? }(settimezone/fiscal_calendar/search_planetonullto 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;frozenis 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. On409/428, re-read and retry. - Create endpoints are not idempotent unless a caller-provided
codeis 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 g3nretailstackFacilities + 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 g3nretailstackMembers + 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 withexpected_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 withexpected_revision. - Legal status
POST /facility/legal/status— status: active↔inactive→doomed. - Logical create
POST /facility/logical/create— code unique per org; requiresphysical_guid+legal_guid(same org); optionalcost_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 withexpected_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? };ROOTis seeded onfacility/logical/create(andzone/createalso ensures it exists for legacy logicals); depth ≤32; code unique per logical; codeROOTis 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_guidin 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_codesrequired; 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’slogical_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; whenlogical_guidis 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’slogical_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? }.captionis editable indraft|active|inactive.logical_guid,channel_code,market_code, and locale fields are editable only whilestatus=draft(immutable after leaving draft).doomedis 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|doomedactive -> inactiveinactive -> active|doomedactive -> doomedis rejected (must goactive -> inactive -> doomed).draft -> activerevalidateschannel_coderegistry, strictmarket_code, locale canonicalization/region matching, and thatlogical_guidexists (not doomed).draft|inactive -> activealso enforces uniqueness of the active external-id binding; conflicts return409 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_revisionis the org record revision). - Secondary add
POST /owner/secondary/add—{ org_guid, user_guid, expected_revision?, reason?, actor?, session_guid? }(expected_revisionrequired when updating an existing owner record). - Secondary remove
POST /owner/secondary/remove—{ org_guid, user_guid, expected_revision?, reason?, actor?, session_guid? }(expected_revisionrequired 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 returns404 not-found). - Creates the org member record (doomed members cannot be re-added).
- Bound invites: accept requires the session user to match
- 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_revisionrequired 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 whenuser_guidomitted). - Resolve
POST /member/resolve— POST read (no GET).{ org_guid|orgcode, session_guid, logical_guid? }→{ is_owner, roles[], org_status, member_state, ... }(enforceseffective_from/effective_toat resolve time; whenlogical_guidis provided, returns facility-scopedlogical_access+logical_rolesand 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_toare enforced by facility-grant checks.
- Owner-only.
- 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).
- Owner-only. Requires optimistic concurrency (
- 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_guidscopes 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=nullclears). - 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_revisionrequired 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; functionofm_orgstatusset; supportsfrozenfor UTL offboarding freeze). - ownerPrimarySetAdmin —
{ org_guid, user_guid, expected_revision, reason? }→{ org_guid, user_guid, primary_owner: true, revision }(operator-only direct Lambda; functionofm_ownerprimaryset_admin). - ownerStateSetAdmin —
{ org_guid, user_guid, state, expected_revision, reason? }→{ org_guid, user_guid, state, revision }(operator-only direct Lambda; functionofm_ownerstateset_admin). - memberStateSetAdmin —
{ org_guid, user_guid, state, expected_revision, reason? }→{ org_guid, user_guid, state, revision }(operator-only direct Lambda; functionofm_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 g3nretailstackg3n 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 g3nretailstackg3n 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 JSONnext_token, theng3n 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.