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
Authorizationheader (except public auth endpoints).User ID Format: User IDs are Amazon aliases (e.g.,
johndoe,janesmith), NOT email addresses.
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.
The NBS platform defines three 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 |
| 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.
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
These endpoints handle the OAuth 2.0 PKCE flow for human users. They are public (no Bearer token required).
Note: The
GET /auth/configendpoint has been removed. OAuth configuration (client_id, authorization_endpoint, scope) is now embedded inconfig.jsondeployed per environment to S3. See Environment Configuration for staging and production values.
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.
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_tokenon refresh — onlyaccess_token+refresh_token. Theuser_infoin the refresh response contains onlysubandfullname(looked up from the organization table). The frontend should useaccess_tokenfor 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
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_infoin verify-token response containssub,fullname,appId,userRole,effectiveRole, andactiveDelegations. Identity claims likedepartment, andmanagerare 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-tokenflow. There is no need for app users to call a separate login tracking API.
M2M (Machine-to-Machine) clients authenticate using AWS Cognito Client Credentials flow.
client_id and client_secret via the Cognito token endpointAuthorization: Bearer <cognito_access_token>?auth_source=cognito_m2m query parameter to all API callsclient_id from the Cognito token is used as the user's sub (identity)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_m2mquery parameter tells the API to verify the token against Cognito JWKS instead of Federate JWKS.
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
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,
roleis"platform_admin"and all apps are returned.
All file operation endpoints require app-level permissions and are scoped to {appId}.
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
└── {anotherUserId}/
| Your Role | .private |
.public |
Own Directory | Other User's Directory |
|---|---|---|---|---|
| Owner | Read ✅ Write ✅ | Read ✅ Write ✅ | Read ✅ Write ✅ | Read ✅ Write ✅ (active only) |
| Manager | ❌ | Read ✅ | Read ✅ Write ✅ | Subordinates: Read ✅ Write ✅ |
| Member | ❌ | Read ✅ | Read ✅ Write ✅ | ❌ |
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).
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
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-Typeheader in your upload request MUST exactly match thecontentTypeyou specified. Mismatched Content-Type will result in a403 SignatureDoesNotMatcherror.
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
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
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
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"
}
}
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
Delegations allow users to temporarily grant their file access permissions to other users.
| Type | Permissions Granted | Use Case |
|---|---|---|
| FULL | list, upload, download, delete | Vacation coverage, full handover |
| READ_ONLY | list, download | Audit access, review-only sharing |
effectiveRole is elevated to the grantor's role (for FULL delegations)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
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"
}
]
}
}
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
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.
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.
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
}
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 |
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_ERROR — userId parameter is missing or empty
- 401 — Missing or invalid Bearer token
- 404 NOT_FOUND — User not found in organization table or has status: deleted
This endpoint allows registered apps (both human users and M2M clients) to write user operation logs to the platform's audit system.
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
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-..."
}
}
| 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 |
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 |
| 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 ❌ | ❌ | ❌ |
| 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 |
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 |
| 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 |
M2M clients follow the same permission model as human users:
client_id as a user in the appowner, manager, or member)?auth_source=cognito_m2m on all API callsclient_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
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
});