NBS Unified Platform — App User API Reference

Audience: App Users — including App Owner, App Manager, App Member, and M2M (Machine-to-Machine) clients

Base URL: https://{api-gateway-domain}/{stage}

Authentication: All endpoints require a valid Bearer token in the Authorization header (except public auth endpoints).

User ID Format: User IDs are Amazon aliases (e.g., johndoe, janesmith), NOT email addresses.


Table of Contents

  1. Role Overview
  2. Authentication (Auth)
  3. Human User Authentication (OAuth 2.0 PKCE)
  4. M2M Authentication (Cognito Client Credentials)
  5. App Discovery
  6. File Operations
  7. Delegation Management
  8. User Search
  9. External Audit Logging
  10. Team-Based Access Control
  11. Error Handling
  12. Permission & Access Control Reference
  13. Environment Configuration

Note on Login Tracking: Login events are automatically recorded during the token exchange flow (POST /auth/exchange-token). There is no need to call a separate login tracking API.


1. Role Overview

The NBS platform defines the following app-level roles with different permission scopes:

Role Description Key Capabilities
App Owner Full data access across the entire app Access all directories (.public, .private, and all user directories), create delegations for others, batch copy files, view all delegations
App Manager Mid-level access with subordinate visibility Access own directory + subordinates' directories + .public, file operations within scope
App Member Standard user with personal workspace Access own directory + .public (read-only), file operations in own directory
Team Member (via binding) Access granted through team binding Same as App Member — access own directory + .public (read-only). Access is automatic when their team is bound to the app.
Team Owner Manages specific teams within an app View team members, manage team settings for assigned teams. Assigned by App Admin or Platform Admin.
Team Leader (capability) Org hierarchy team leader Elevated visibility within their team context. Detected automatically from org hierarchy teamLeader field. Returned as a capability in /permissions endpoint.
M2M Client Machine-to-machine service account Authenticated via Cognito client credentials, can call external audit API and other authorized endpoints

Note: App and user management (adding/removing users, updating roles, etc.) is performed by the App Admin role, which is documented in the Platform Admin API Reference. The App Owner role described here focuses on data access privileges within the app.

Effective Role via Delegation

Users may have an effective role higher than their native role through active delegations: - A member with a FULL delegation from an owner gets effectiveRole: "owner" - A member with a READ_ONLY delegation can read (but not write) the grantor's directories - The effectiveRole is returned in token exchange/verify responses


2. Authentication (Auth)

2.1 Human User Authentication (OAuth 2.0 PKCE)

These endpoints handle the OAuth 2.0 PKCE flow for human users. They are public (no Bearer token required).

Note: The GET /auth/config endpoint has been removed. OAuth configuration (client_id, authorization_endpoint, scope) is now embedded in config.json deployed per environment to S3. See Environment Configuration for staging and production values.


2.1.1 Exchange Authorization Code for Tokens

Exchange an OAuth authorization code for access, ID, and refresh tokens. This is the primary login endpoint.

POST /auth/exchange-token

Request Body:

{
  "code": "authorization_code_from_redirect",
  "code_verifier": "pkce_code_verifier_string",
  "redirect_uri": "https://your-app.example.com/callback",
  "app_id": "my-app",
  "app_name": "My Application"
}
Field Type Required Description
code string OAuth authorization code from redirect
code_verifier string PKCE code verifier (generated during authorization)
redirect_uri string Must exactly match the redirect URI used in authorization request
app_id string Application ID to check permissions against
app_name string Display name (used for login tracking)

Response 200 OK:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "id_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-03-21T09:00:00.000Z",
    "user_info": {
      "sub": "johndoe",
      "email": "johndoe@amazon.com",
      "fullname": "John Doe",
      "department": "NBS",
      "manager": "Jane Smith",
      "appId": "my-app",
      "userRole": "member",
      "effectiveRole": "owner",
      "activeDelegations": [
        {
          "grantorId": "janesmith",
          "grantorRole": "owner",
          "delegationType": "FULL",
          "delegationId": "uuid-..."
        }
      ]
    }
  }
}

Key Fields in user_info: - sub — User's Amazon alias (e.g., johndoe) - userRole — Native role in the app (owner, manager, member, or null for platform apps) - effectiveRole — Highest role considering active delegations - activeDelegations — List of active delegations elevating the user's permissions

Access Mode Behavior: - Whitelist apps: User must have explicit permission → 403 if not authorized - Public apps: All authenticated users get access with default role member - Platform apps (app-lobby, app-store): Skip permission check, userRole: null

Error Responses: - 400 — Missing required parameters - 401 — Invalid or expired authorization code (INVALID_CODE) - 403 — User not authorized for whitelist app (ACCESS_DENIED)

Note: Login events are automatically tracked during token exchange.


2.1.2 Refresh Access Token

Refresh an expired access token using a refresh token.

POST /auth/refresh-token

Headers:

Authorization: Bearer <refresh_token>

Request Body:

{
  "app_id": "my-app"
}
Field Type Required Description
app_id string Application ID for role re-evaluation

Response 200 OK:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "id_token": null,
    "refresh_token": "eyJhbGciOi...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-03-21T09:00:00.000Z",
    "user_info": {
      "sub": "johndoe",
      "fullname": "John Doe"
    }
  }
}

Important: Amazon Federate does NOT return id_token on refresh — only access_token + refresh_token. The user_info in the refresh response contains only sub and fullname (looked up from the organization table). The frontend should use access_token for Bearer auth (OAuth 2.0 best practice).

Error Responses: - 400 — Missing parameters or token decryption error - 401 — Invalid or expired refresh token (INVALID_REFRESH_TOKEN) - 403 — User no longer authorized for the app


2.1.3 Verify Token (Introspection)

Verify a token and return trusted identity context from the database. Use this to validate session state.

POST /auth/verify-token

Headers:

Authorization: Bearer <access_token>

Request Body:

{
  "app_id": "my-app"
}

Response 200 OK:

{
  "data": {
    "valid": true,
    "user_info": {
      "sub": "johndoe",
      "appId": "my-app",
      "userRole": "member",
      "effectiveRole": "owner",
      "activeDelegations": [...],
      "fullname": "John Doe"
    }
  }
}

Note: The user_info in verify-token response contains sub, fullname, appId, userRole, effectiveRole, and activeDelegations. Identity claims like email, department, and manager are only available in the initial exchange-token response.

Error Responses: - 401 TOKEN_EXPIRED — Token has expired (client should call refresh-token) - 401 INVALID_TOKEN — Token signature invalid or tampered (re-login required) - 403 ACCESS_DENIED — User not in app whitelist (no retry) - 500 SYSTEM_ERROR — Database/KMS failure


Login Tracking: Login events are automatically recorded during the POST /auth/exchange-token flow. There is no need for app users to call a separate login tracking API.


2.2 M2M Authentication (Cognito Client Credentials)

M2M (Machine-to-Machine) clients authenticate using AWS Cognito Client Credentials flow.

How M2M Authentication Works

  1. Obtain a Cognito access token using your client_id and client_secret via the Cognito token endpoint
  2. Include the token as Authorization: Bearer <cognito_access_token>
  3. Add ?auth_source=cognito_m2m query parameter to all API calls
  4. The client_id from the Cognito token is used as the user's sub (identity)

M2M Request Format

POST /apps/{appId}/audit/log?auth_source=cognito_m2m
Authorization: Bearer <cognito_access_token>
Content-Type: application/json

{
  "operation": "data_export",
  "status": "success",
  "operationDetails": { "recordCount": 1500 }
}

Important: The auth_source=cognito_m2m query parameter tells the API to verify the token against Cognito JWKS instead of Federate JWKS.

M2M Permissions

M2M clients are identified by their client_id (which becomes their sub). To grant an M2M client access to an app: 1. Register the M2M client's client_id as a user in the app's permissions table 2. Assign appropriate role (owner, manager, or member) 3. The M2M client can then call any API endpoint its role permits


3. App Discovery

3.1 List My Apps

Get all apps accessible by the current authenticated user.

GET /apps

Headers:

Authorization: Bearer <access_token>

Query Parameters:

Parameter Type Default Description
includeDeleted boolean false Include soft-deleted apps

Behavior by Role: - Platform Admin: Returns ALL apps across the platform with userRole: "platform_admin" - Regular User: Returns whitelist apps (where user has explicit permission) + all public apps

Response 200 OK:

{
  "data": {
    "apps": [
      {
        "appId": "my-app",
        "appName": "My Application",
        "description": "A data insights tool",
        "status": "active",
        "accessMode": "whitelist",
        "category": "data-insights",
        "userRole": "owner",
        "userCount": 15,
        "adminCount": 2,
        "entryUrl": "https://my-app.example.com",
        "iconUrl": "https://presigned-url...",
        "createdAt": "2026-01-15T10:00:00.000Z"
      },
      {
        "appId": "public-dashboard",
        "appName": "Public Dashboard",
        "status": "active",
        "accessMode": "public",
        "userRole": "member",
        "userCount": 0,
        "adminCount": 1
      }
    ],
    "role": "app_user",
    "totalApps": 2
  }
}

Note: For platform admins, role is "platform_admin" and all apps are returned.


4. File Operations

All file operation endpoints require app-level permissions and are scoped to {appId}.

Directory Structure

Each app has the following directory structure in S3:

{appId}/
├── .private/              ← Owner-only access (read/write)
├── .public/               ← All members can read; only owners can write
├── {userId}/              ← Personal workspace for each user
│   ├── subfolder/
│   └── document.pdf
├── .teams/{teamId}/       ← Team directory (auto-created when team is bound)
│   ├── .public/           ← Team shared files (members: read; team leader/owner: read+write)
│   └── .private/          ← Team leader and team_owner only (read/write)
└── {anotherUserId}/

Access Control Summary

Your Role .private .public Own Directory Other User's Dir .teams/{id}/.public .teams/{id}/.private
Owner Read ✅ Write ✅ Read ✅ Write ✅ Read ✅ Write ✅ Read ✅ Write ✅ Read ✅ Write ✅ Read ✅ Write ✅
Manager Read ✅ Read ✅ Write ✅ Subordinates only ❌ (unless also team member)
Member Read ✅ Read ✅ Write ✅ ❌ (unless also team member)
Team Owner (explicit) Read ✅ Read ✅ Write ✅ Managed team members Read ✅ Write ✅ (managed teams) Read ✅ Write ✅ (managed teams)
Team Member (auto) Read ✅ Read ✅ Write ✅ Read ✅ (own team only)
Team Leader (auto) Read ✅ Read ✅ Write ✅ Read ✅ Write ✅ (led team + descendants) Read ✅ Write ✅ (led team + descendants)
Sub-team Member (recursive) Read ✅ Read ✅ Write ✅ Own sub-team: Read ✅; Parent team: only if allowChildAccessToDir=true

Important Notes:

Delegation: If you have an active delegation from another user, you can access their directories based on the delegation type (FULL = read+write, READ_ONLY = read only).


4.1 List Files

List files and directories within an app.

GET /apps/{appId}/files

Required Permission: app:files:list

Query Parameters:

Parameter Type Default Description
directory string Target directory (required for members, optional for owners/managers)
recursive boolean false List all files recursively (owner only)
search string Wildcard pattern for filename matching (e.g., *report*.xlsx)
includeDeleted boolean false Include soft-deleted files (owner only)
continuationToken string Pagination token
maxKeys integer 1000 Maximum number of keys to return

Examples:

# Member: list own files
GET /apps/my-app/files?directory=johndoe

# Member: list files in a subfolder
GET /apps/my-app/files?directory=johndoe/reports

# Member: list public files
GET /apps/my-app/files?directory=.public

# Owner: list all top-level directories
GET /apps/my-app/files

# Owner: recursive search across all directories
GET /apps/my-app/files?recursive=true&search=*quarterly*.xlsx

# Manager: list scoped directories (own + subordinates + .public)
GET /apps/my-app/files

Response 200 OK (with directory specified):

{
  "data": {
    "files": [
      {
        "key": "my-app/johndoe/report.pdf",
        "filename": "report.pdf",
        "size": 1048576,
        "lastModified": "2026-03-20T15:30:00.000Z",
        "owner": "johndoe"
      }
    ],
    "directories": [
      {
        "directory": "reports",
        "prefix": "my-app/johndoe/reports/"
      }
    ],
    "totalFiles": 1,
    "totalDirectories": 1,
    "totalSize": 1048576,
    "prefix": "my-app/johndoe/"
  }
}

Response 200 OK (Manager scoped list, no directory):

{
  "data": {
    "directories": [
      { "directory": "johndoe", "prefix": "my-app/johndoe/" },
      { "directory": "subordinate1", "prefix": "my-app/subordinate1/" },
      { "directory": ".public", "prefix": "my-app/.public/" }
    ],
    "totalDirectories": 3,
    "prefix": "my-app/"
  }
}

Error Responses: - 400 MISSING_DIRECTORY — Directory parameter required for non-owner users - 403 ACCESS_DENIED — No access to the requested directory - 403 PERMISSION_DENIED — Recursive/includeDeleted only for owners


4.2 Upload File (Get Upload URL)

Generate a presigned S3 PUT URL for file upload.

POST /apps/{appId}/files/upload

Required Permission: app:files:upload

Request Body:

{
  "directory": "johndoe",
  "filename": "quarterly-report.pdf",
  "contentType": "application/pdf",
  "expires_in": 900
}
Field Type Required Description
directory string Target directory (your alias, .public, or subdirectory path)
filename string Filename (no slashes allowed)
contentType string MIME type (e.g., application/pdf, image/png)
expires_in integer URL expiration in seconds (default: 900, max: 3600)

Response 200 OK:

{
  "data": {
    "uploadUrl": "https://nbs-data-storage.s3.ap-southeast-2.amazonaws.com/my-app/johndoe/quarterly-report.pdf?X-Amz-...",
    "expiresIn": 900,
    "requiredHeaders": {
      "Content-Type": "application/pdf"
    },
    "s3Key": "my-app/johndoe/quarterly-report.pdf"
  }
}

How to Upload:

# Use the presigned URL with the EXACT Content-Type from requiredHeaders
curl -X PUT \
  -H "Content-Type: application/pdf" \
  --data-binary @quarterly-report.pdf \
  "https://nbs-data-storage.s3.ap-southeast-2.amazonaws.com/my-app/johndoe/quarterly-report.pdf?X-Amz-..."

⚠️ Critical: The Content-Type header in your upload request MUST exactly match the contentType you specified. Mismatched Content-Type will result in a 403 SignatureDoesNotMatch error.

Error Responses: - 400 MISSING_CONTENT_TYPE — contentType is required - 400 INVALID_FILENAME — Filename contains invalid characters - 403 ACCESS_DENIED — No write access to the directory


4.3 Download File (Get Download URL)

Generate a presigned S3 GET URL for file download.

POST /apps/{appId}/files/download

Required Permission: app:files:download

Request Body:

{
  "directory": "johndoe",
  "filename": "quarterly-report.pdf",
  "expires_in": 300
}
Field Type Required Description
directory string Source directory (including subdirectory path if needed)
filename string Filename to download (no slashes)
expires_in integer URL expiration in seconds (default: 300, max: 3600)

Response 200 OK:

{
  "data": {
    "downloadUrl": "https://nbs-data-storage.s3.ap-southeast-2.amazonaws.com/my-app/johndoe/quarterly-report.pdf?X-Amz-...",
    "expiresIn": 300,
    "filename": "quarterly-report.pdf"
  }
}

Error Responses: - 403 ACCESS_DENIED — No read access to the directory - 404 NOT_FOUND — File not found


4.4 Delete File

Delete a file from S3.

DELETE /apps/{appId}/files

Required Permission: app:files:delete

Request Body:

{
  "directory": "johndoe",
  "filename": "old-report.pdf"
}
Field Type Required Description
directory string Directory containing the file
filename string Filename to delete (no slashes)

Response 200 OK:

{
  "data": {
    "message": "File deleted successfully",
    "deletedKey": "my-app/johndoe/old-report.pdf"
  }
}

Error Responses: - 403 ACCESS_DENIED — No write access to the directory


4.5 Copy File (Server-Side)

Copy a single file within the same app using S3 server-side copy.

POST /apps/{appId}/files/copy

Required Permission: app:files:copy

Request Body:

{
  "sourceDirectory": ".public/templates",
  "sourceFilename": "baseline.xlsx",
  "targetDirectory": "johndoe/reports",
  "targetFilename": "my-baseline.xlsx"
}
Field Type Required Description
sourceDirectory string Source directory (can include subdirectory path)
sourceFilename string Source filename (no slashes)
targetDirectory string Target directory (can include subdirectory path)
targetFilename string Target filename (no slashes)

Access Control: - You need read access to the source directory - You need write access to the target directory - Members can copy from .public or own directory to own directory

Response 200 OK:

{
  "data": {
    "sourceKey": "my-app/.public/templates/baseline.xlsx",
    "targetKey": "my-app/johndoe/reports/my-baseline.xlsx",
    "message": "File copied successfully"
  }
}

4.6 Batch Copy Files (Owner Only)

Batch copy all files from one directory to another. Owner only.

POST /apps/{appId}/files/copy-all

Required Permission: app:files:copy + Owner role

Request Body:

{
  "sourceDirectory": "departing-user",
  "targetDirectory": ".private/backup-20260321"
}
Field Type Required Description
sourceDirectory string Source directory to copy from
targetDirectory string Target directory to copy to

Response 200 OK:

{
  "data": {
    "copiedFiles": [
      { "sourceKey": "my-app/departing-user/file1.pdf", "targetKey": "my-app/.private/backup-20260321/file1.pdf" }
    ],
    "failedFiles": [],
    "totalCopied": 42
  }
}

Error Responses: - 403 PERMISSION_DENIED — Only app owners can batch copy


5. Delegation Management

Delegations allow users to temporarily grant their file access permissions to other users.

Delegation Types

Type Permissions Granted Use Case
FULL list, upload, download, delete Vacation coverage, full handover
READ_ONLY list, download Audit access, review-only sharing

How Delegation Works

  1. Grantor (the person sharing access) must have a native role in the app (not delegated)
  2. Delegatee (the person receiving access) must be an active user in the app
  3. Delegations can have an optional expiry date
  4. The delegatee's effectiveRole is elevated to the grantor's role (for FULL delegations)
  5. Transitive delegation is not allowed (you cannot delegate delegated permissions)

5.1 Create Self-Delegation

Delegate your own permissions to another user.

POST /apps/{appId}/delegations/self

Access: Any authenticated app user with a native role

Request Body:

{
  "delegateeId": "colleague",
  "delegationType": "FULL",
  "expiry": "2026-04-01T00:00:00.000Z"
}
Field Type Required Description
delegateeId string Amazon alias of the person receiving the delegation
delegationType string FULL or READ_ONLY
expiry string ISO 8601 expiry timestamp (no expiry if omitted)

Response 201 Created:

{
  "data": {
    "delegationId": "uuid-...",
    "appId": "my-app",
    "grantorId": "johndoe",
    "delegateeId": "colleague",
    "delegationType": "FULL",
    "status": "active",
    "expiry": "2026-04-01T00:00:00.000Z",
    "createdAt": "2026-03-21T08:00:00.000Z",
    "createdBy": "johndoe"
  }
}

Error Responses: - 400 — Missing fields, self-delegation (delegateeId = yourself), invalid type - 403 — No native role, or attempting transitive delegation - 404 — Delegatee not found or inactive in the app - 409 — Duplicate active delegation already exists


5.2 Query My Delegations

Get all delegations where you are either the grantor or delegatee.

GET /apps/{appId}/delegations/mine

Access: Any authenticated app user

Query Parameters:

Parameter Type Default Description
status string active active, revoked, or all

Response 200 OK:

{
  "data": {
    "delegations": [
      {
        "delegationId": "uuid-...",
        "appId": "my-app",
        "grantorId": "johndoe",
        "delegateeId": "colleague",
        "delegationType": "FULL",
        "status": "active",
        "expiry": "2026-04-01T00:00:00.000Z",
        "createdAt": "2026-03-21T08:00:00.000Z",
        "createdBy": "johndoe"
      }
    ]
  }
}

5.3 Revoke Delegation

Revoke an active delegation. Can be done by the grantor, delegatee, or app owner.

DELETE /apps/{appId}/delegations/{delegationId}

Access: Grantor, delegatee, or App Owner

Response 200 OK:

{
  "data": {
    "message": "Delegation revoked successfully",
    "delegationId": "uuid-..."
  }
}

Error Responses: - 400 — Already revoked - 403 — Not authorized to revoke (not grantor, delegatee, or owner) - 404 — Delegation not found


6.1 Search App Users

Search for active users within an app (for delegation target selection, etc.).

GET /apps/{appId}/users/search?query=john

Access: Any authenticated app user

Query Parameters:

Parameter Type Required Description
query string Search prefix for userId or substring for name

Response 200 OK:

{
  "data": {
    "users": [
      {
        "userId": "johndoe",
        "alias": "johndoe",
        "name": "John Doe",
        "email": "johndoe@amazon.com",
        "role": "owner"
      }
    ]
  }
}

Note: Returns up to 20 matching users. Only active users are returned.


6.2 Search Organization Users (App Admin)

Search the organization directory for users to add to an app.

GET /apps/{appId}/org-users/search?query=john

Access: Users with app:users:manage permission (App Admin)

Query Parameters:

Parameter Type Required Description
query string Search prefix for userId or substring for employeeName

Response 200 OK:

{
  "data": {
    "users": [
      { "alias": "johndoe", "name": "John Doe", "status": "active" },
      { "alias": "johnsmith", "name": "John Smith", "status": "active" }
    ]
  }
}

Note: Only non-deleted organization members are returned. Returns up to 20 results.


6.3 Get Organization User Info

Look up a specific user's organization context by exact userId. Returns the user's direct manager and all active direct reportees (including each reportee's own reportee count).

GET /org/users?userId={userId}

Access: Any authenticated user (no admin role required)

Query Parameters:

Parameter Type Required Description
userId string Exact Amazon alias to look up (case-sensitive)

Example:

GET /org/users?userId=johndoe

Response 200 OK:

{
  "userId": "johndoe",
  "alias": "johndoe",
  "name": "John Doe",
  "email": "johndoe@amazon.com",
  "manager": {
    "userId": "janesmith",
    "name": "Jane Smith"
  },
  "reportees": [
    {
      "userId": "alicejones",
      "name": "Alice Jones",
      "reporteeCount": 3
    },
    {
      "userId": "boblee",
      "name": "Bob Lee",
      "reporteeCount": 0
    }
  ],
  "activeReporteeCount": 2,
  "jobLevelName": "6"
}

Response Fields:

Field Type Description
userId string Amazon alias (same as alias)
alias string Amazon alias
name string Full display name from org table
email string Derived as {userId}@amazon.com
manager object | null Direct manager's userId and name; null if top of hierarchy
reportees array All active direct reports, each with userId, name, and reporteeCount
activeReporteeCount integer Total count of active direct reports
reportees[].reporteeCount integer Number of active direct reports that this reportee themselves manages
jobLevelName string | null Job level (e.g., "4", "5", "6"). Only present if synced from RCM parquet.

Manager Derivation Logic:

The manager is determined from the pre-computed managerId field written at sync time: - If level_N == userId, then level_{N-1} is the manager - If the user appears at level_1 (top of hierarchy), manager is null - If the user does not appear in any level field, level_4 is treated as the manager

Error Responses: - 400 VALIDATION_ERRORuserId parameter is missing or empty - 401 — Missing or invalid Bearer token - 404 NOT_FOUND — User not found in organization table or has status: deleted


7. External Audit Logging

This endpoint allows registered apps (both human users and M2M clients) to write user operation logs to the platform's audit system.

7.1 Log External Audit Event

POST /apps/{appId}/audit/log

Access: Any authenticated user (Federate) or M2M client (Cognito)

For M2M clients, add ?auth_source=cognito_m2m:

POST /apps/{appId}/audit/log?auth_source=cognito_m2m

Request Body:

{
  "operation": "data_export",
  "status": "success",
  "operationDetails": {
    "recordCount": 1500,
    "exportFormat": "csv",
    "targetBucket": "analytics-output"
  },
  "filePath": "exports/2026-03/quarterly-report.csv",
  "errorMessage": ""
}
Field Type Required Description
operation string Operation name (alphanumeric, _, :, ., -, /; max 100 chars)
status string success, failed, error, or partial (default: success)
operationDetails object Free-form JSON metadata (max 4 KB serialized)
filePath string Related file path (max 500 chars, no control characters)
errorMessage string Error description when status ≠ success (max 1000 chars)

Validation Rules: - operation must match ^[a-zA-Z0-9_:./-]{1,100}$ - operationDetails serialized JSON must be ≤ 4 KB - filePath must not contain control characters and ≤ 500 chars - appId is taken from the URL path (trusted), never from the body

Response 200 OK:

{
  "data": {
    "message": "Audit log recorded",
    "userId": "johndoe",
    "appId": "my-app",
    "operation": "data_export",
    "timestamp": "2026-03-21T08:00:00.000Z"
  }
}

Source Tagging: - Human user (Federate): source = "external_app" - M2M client (Cognito): source = "external_app_m2m"

Error Responses: - 400 — Missing operation, invalid characters, or payload too large - 401 — Unable to determine userId from token - 404 — App not found in registry


8. Team-Based Access Control

The NBS platform supports team-based access control, allowing apps to bind teams from the organization hierarchy. When a team is bound to an app, all team members automatically receive app access permissions.

Key Concepts:


8.1 Search Teams

Search for teams by name. Used when binding teams to an app.

GET /teams/search?query={query}

Access: Any authenticated user

Query Parameters:

Parameter Type Required Description
query string Team name search keyword (min 2 characters)

Response 200 OK:

{
  "teams": [
    {
      "teamId": "12345",
      "teamName": "NBS Team",
      "parentTeamId": "99999",
      "depth": 2
    }
  ]
}

Error Responses: - 400 — Missing or too short query parameter - 401 — Missing or invalid Bearer token


8.2 List App Teams

List all teams bound to an app. Response detail level varies by role.

GET /apps/{appId}/teams

Access: Any app member (must have a role in the app)

Path Parameters:

Parameter Type Description
appId string App identifier

Response 200 OK (privileged user — owner/team_owner/platform admin):

{
  "teams": [
    {
      "teamId": "12345",
      "teamName": "NBS Team",
      "parentTeamId": "99999",
      "depth": 2,
      "recursive": true,
      "allowChildAccessToDir": false,
      "memberCount": 15
    }
  ],
  "total": 1
}

Response 200 OK (regular member — limited view):

{
  "teams": [
    {
      "teamId": "12345",
      "teamName": "NBS Team",
      "parentTeamId": "99999"
    }
  ],
  "total": 1
}

Note: Regular members only see basic team info (teamId, teamName, parentTeamId). Privileged users see full binding configuration and member counts.

Error Responses: - 403 — User is not a member of the app - 404 — App not found


8.3 Get Team Members

List members of a specific team bound to an app.

GET /apps/{appId}/teams/{teamId}/members

Access: App Owner, Team Owner (of specified team), or Platform Admin

Path Parameters:

Parameter Type Description
appId string App identifier
teamId string Team identifier

Query Parameters:

Parameter Type Default Description
directOnly boolean false Only return direct members (overrides binding's recursive flag)

Response 200 OK:

{
  "teamId": "12345",
  "members": [
    {
      "userId": "johndoe",
      "membershipLevel": 0
    }
  ],
  "total": 5
}

Note: membershipLevel indicates the depth from the bound team. Level 0 = direct member, level 1+ = sub-team member (only included when binding has recursive: true).

Error Responses: - 403 — Not authorized (must be app owner, team_owner of this team, or platform admin) - 404 — App not found


8.4 Get User's Teams

Get all teams a user belongs to within a specific app context.

GET /users/{userId}/teams?appId={appId}

Access: Self (own userId), App Owner, or Team Owner

Path Parameters:

Parameter Type Description
userId string Target user's Amazon alias

Query Parameters:

Parameter Type Required Description
appId string App identifier to check teams for

Response 200 OK:

{
  "userId": "johndoe",
  "appId": "my-app",
  "teams": [
    {
      "teamId": "12345",
      "teamName": "NBS Team",
      "membershipLevel": 0
    }
  ]
}

Error Responses: - 400 — Missing required appId query parameter - 403 — Not authorized to view target user's teams - 404 — App not found


8.5 Bind Team to App

Bind a team to an app, granting all team members automatic access.

POST /apps/{appId}/teams

Access: App Admin or Platform Admin

Path Parameters:

Parameter Type Description
appId string App identifier

Request Body:

{
  "teamId": "12345",
  "recursive": true,
  "allowChildAccessToDir": false
}
Field Type Required Description
teamId string Team ID to bind (must exist in org hierarchy)
recursive boolean Include sub-team members in access grant
allowChildAccessToDir boolean Allow sub-team members to access team directory (default: false)

Response 201 Created:

{
  "message": "Team bound successfully",
  "appId": "my-app",
  "teamId": "12345",
  "recursive": true,
  "allowChildAccessToDir": false
}

Error Responses: - 400 — Missing required fields or invalid types - 403 — Not an app admin or platform admin - 404 — Team not found in org hierarchy - 409 — Team already bound to this app


8.6 Unbind Team from App

Remove a team binding from an app. Team members lose automatic access.

DELETE /apps/{appId}/teams/{teamId}

Access: App Admin or Platform Admin

Path Parameters:

Parameter Type Description
appId string App identifier
teamId string Team identifier to unbind

Response 200 OK:

{
  "message": "Team unbound successfully",
  "appId": "my-app",
  "teamId": "12345"
}

Error Responses: - 403 — Not an app admin or platform admin - 404 — Team binding not found


8.7 Get User Capabilities

Get a user's full permission context including team-based capabilities.

GET /apps/{appId}/users/{userId}/permissions

Access: Any authenticated user (typically used for self-check)

Path Parameters:

Parameter Type Description
appId string App identifier
userId string Target user's Amazon alias

Response 200 OK:

{
  "userId": "johndoe",
  "appId": "my-app",
  "role": "member",
  "accessVia": "team_binding",
  "capabilities": [
    {
      "type": "team_leader",
      "teamId": "12345",
      "teamName": "NBS Team"
    }
  ]
}

Key Fields:

Field Description
accessVia personal_permission (direct role) or team_binding (via team)
capabilities Array of special capabilities. team_leader indicates the user leads a bound team.

Note: The team_leader capability is derived from the teamLeader field in the team hierarchy. Team leaders have elevated visibility within their team context.

Error Responses: - 404 — App or user not found


9. Error Handling

All error responses follow a consistent format:

{
  "statusCode": 403,
  "body": {
    "error": "Access denied: You can only access your own directory and the .public directory",
    "errorCode": "ACCESS_DENIED",
    "errorId": "ERR-uuid-..."
  }
}

Common Error Codes

HTTP Status Error Code Description Action
400 VALIDATION_ERROR Missing or invalid parameters Fix request parameters
400 MISSING_CONTENT_TYPE contentType required for upload Add contentType to body
400 MISSING_DIRECTORY Directory required for non-owner Add directory parameter
400 INVALID_FILENAME Filename contains invalid characters Fix filename
401 INVALID_TOKEN Token expired or invalid Refresh token or re-login
401 TOKEN_EXPIRED Token has expired Call refresh-token endpoint
403 ACCESS_DENIED No access to resource Check your role and directory access
403 PERMISSION_DENIED Feature restricted to higher role Contact app admin
404 NOT_FOUND Resource not found Verify resource exists
409 CONFLICT Resource already exists Check for duplicates
500 INTERNAL_ERROR Server-side error Retry or contact admin

10. Permission & Access Control Reference

10.1 Role Permissions

Data Access Permissions (applicable to all app-level roles):

Permission Owner Manager Member Description
app:files:list List files in accessible directories
app:files:upload Generate upload presigned URLs
app:files:download Generate download presigned URLs
app:files:delete Delete files in accessible directories
app:files:copy Copy files between accessible directories

App Admin Permissions (App Admin only — see Platform Admin API Reference for details):

Permission Description
app:admin Administrative operations (manage app settings)
app:users:manage Add/remove/update users in the app
app:config:view View app configuration and user list

10.2 Directory Access Matrix

Directory Owner Manager Member
.private Read ✅ Write ✅
.public Read ✅ Write ✅ Read ✅ Write ❌ Read ✅ Write ❌
Own directory Read ✅ Write ✅ Read ✅ Write ✅ Read ✅ Write ✅
Subordinate's directory N/A Read ✅ Write ✅ N/A
Other user's directory Read ✅ Write ✅ (active)
Soft-deleted user dir Read ✅ Write ❌

10.3 Delegation Permission Mapping

Delegation Type Permissions Granted
FULL app:files:list, app:files:upload, app:files:download, app:files:delete
READ_ONLY app:files:list, app:files:download

10.4 Delegation Directory Access

When accessing directories via delegation, the access is based on the grantor's role:

Grantor Role Delegatee Can Access
Owner All directories (.private, .public, all user dirs)
Manager Grantor's own directory + grantor's subordinates' directories
Member Grantor's own directory only

10.5 Access Mode Behavior

Access Mode Who Can Access How to Add Users
Whitelist Only users explicitly added to the app Add via POST /apps/{appId}/users
Public All authenticated users (auto member role) Only owner role can be explicitly added

10.6 M2M Client Access

M2M clients follow the same permission model as human users:

  1. Register the M2M client_id as a user in the app
  2. Assign a role (owner, manager, or member)
  3. Use ?auth_source=cognito_m2m on all API calls
  4. The client_id serves as the user's identity (sub)

Supported M2M Operations: - External audit logging (POST /apps/{appId}/audit/log) - File operations (if registered with appropriate role) - Any endpoint that accepts @require_auth authentication


11. Environment Configuration

OAuth configuration is embedded in config.json (deployed per environment to S3). The GET /auth/config endpoint has been removed.

Field Staging / Experimental Production
API Endpoint https://nbsaihub.ags.amazon.dev/prod https://ai.wwgs.amazon.dev/prod
client_id nbsaihubstaging ai-app-store
authorization_endpoint https://idp-integ.federate.amazon.com/api/oauth2/v1/authorize https://idp.federate.amazon.com/api/oauth2/v1/authorize
scope openid profile email openid profile email
redirect_uri https://nbsaihub.ags.amazon.dev/callback.html https://ai.wwgs.amazon.dev/callback.html

Example config.json for production:

{
    "api_gateway_url": "https://ai.wwgs.amazon.dev/prod",
    "oauth_config": {
        "client_id": "ai-app-store",
        "authorization_endpoint": "https://idp.federate.amazon.com/api/oauth2/v1/authorize",
        "scope": "openid profile email",
        "response_type": "code"
    }
}

Pass oauth_config from config.json to the NBSAuth constructor:

const config = await fetch('./config.json').then(r => r.json());
const auth = new NBSAuth({
    apiEndpoint: config.api_gateway_url,
    redirectUri: window.location.origin + '/callback.html',
    appId: 'your-app-id',
    oauthConfig: config.oauth_config  // Required — no /auth/config API call needed
});