{"openapi":"3.1.0","info":{"title":"TableUser Developer API","version":"0.3.0","description":"Register apps, manage users, sync app-sovereign mirrors. API keys are App (registry-scoped) or Platform (ops). Hub: https://tableu.opcglobal.cn/developers/ — keys at https://tableu-admin.opcglobal.cn/developer-api-keys/"},"servers":[{"url":"https://tableu-api.opcglobal.cn"}],"tags":[{"name":"apps"},{"name":"users"},{"name":"sync"},{"name":"hooks"},{"name":"waitlist"},{"name":"admin-keys"},{"name":"admin-console"}],"security":[{"DeveloperApiKey":[]}],"components":{"securitySchemes":{"DeveloperApiKey":{"type":"http","scheme":"bearer","description":"Developer API key (tu_…) — App keys are limited to allowedRegistryIds; Platform keys can merge/link/roles. Mint at https://tableu-admin.opcglobal.cn/developer-api-keys/"},"AdminAccessToken":{"type":"http","scheme":"bearer","description":"OIDC access token for a platform:admin user (admin console SPA)."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}},"RegisterAppRequest":{"type":"object","required":["name","type","redirectUris"],"properties":{"name":{"type":"string","example":"My Product Web"},"type":{"type":"string","enum":["Traditional","SPA","Native","MachineToMachine"]},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1},"postLogoutRedirectUris":{"type":"array","items":{"type":"string","format":"uri"}}}},"RegisterAppResponse":{"type":"object","properties":{"registryId":{"type":"string"},"registryLabel":{"type":"string","example":"ta_019…"},"logtoAppId":{"type":"string"},"logtoAppSecret":{"type":"string","description":"Traditional apps only"},"type":{"type":"string"},"created":{"type":"boolean"},"manifest":{"type":"object","additionalProperties":true},"env":{"type":"object","additionalProperties":{"type":"string"},"description":"Ready-to-paste .env values"}}},"CreateUserRequest":{"type":"object","properties":{"username":{"type":"string"},"email":{"type":"string","format":"email"},"primaryEmail":{"type":"string","format":"email"},"primaryPhone":{"type":"string"},"name":{"type":"string"},"password":{"type":"string"},"role":{"type":"string"},"roleNames":{"type":"array","items":{"type":"string"}},"registryId":{"type":"string","description":"Required for App keys — must be a bound registry"},"appUserId":{"type":"string"}}},"SyncPushRequest":{"type":"object","required":["registryId","users"],"properties":{"registryId":{"type":"string","description":"ta_… or bare registry UUID"},"users":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"username":{"type":"string"},"name":{"type":"string"},"password":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"appUserId":{"type":"string"}}}}}},"MergeRequest":{"type":"object","properties":{"primaryUserId":{"type":"string"},"secondaryUserId":{"type":"string"},"email":{"type":"string","description":"Merge all users sharing this email"},"dryRun":{"type":"boolean","default":false}}},"UnmergeRequest":{"type":"object","required":["primaryUserId","secondaryUserId"],"properties":{"primaryUserId":{"type":"string"},"secondaryUserId":{"type":"string"}}},"WaitlistRequest":{"type":"object","required":["email","orgName","appUrl"],"properties":{"email":{"type":"string","format":"email"},"orgName":{"type":"string"},"appUrl":{"type":"string","format":"uri"},"note":{"type":"string"}}}}},"paths":{"/health":{"get":{"tags":["apps"],"security":[],"summary":"Liveness + active key count","responses":{"200":{"description":"OK"}}}},"/.well-known/tableuser.json":{"get":{"tags":["apps"],"security":[],"summary":"Service discovery for agents","description":"Includes authorizationModel: rolesAre=platform-coarse; resourceAuthority=consuming-app; joinKey=taid.","responses":{"200":{"description":"TableUser metadata"}}}},"/openapi.json":{"get":{"tags":["apps"],"security":[],"summary":"This OpenAPI document","responses":{"200":{"description":"OpenAPI 3.1"}}}},"/v1/apps":{"get":{"tags":["apps"],"summary":"List registered apps from projection (App keys: bound registries only; Platform: all)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"responses":{"200":{"description":"Apps with userCount from SQLite projection"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["apps"],"summary":"Register a new app (App keys auto-bind registryId)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterAppRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterAppResponse"}}}},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{registryRef}":{"get":{"tags":["apps"],"summary":"Get registered app by registry id/label","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"registryRef","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"App manifest"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users":{"get":{"tags":["users"],"summary":"List users (App keys: only users linked to bound registries)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","default":20}},{"name":"search","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated users (includes taid)"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["users"],"summary":"Create user (App keys must link a bound registry)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}}},"responses":{"201":{"description":"Created (includes taid)"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/resolve":{"get":{"tags":["users"],"summary":"Resolve user id / taid / email / alias to canonical user","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"id","in":"query","schema":{"type":"string"}},{"name":"taid","in":"query","schema":{"type":"string"}},{"name":"email","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Resolved user"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/merge":{"post":{"tags":["users"],"summary":"Merge users (Platform keys only)","x-tableuser-key-class":"platform","security":[{"DeveloperApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeRequest"}}}},"responses":{"200":{"description":"Merge plan or result"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/unmerge":{"post":{"tags":["users"],"summary":"Unmerge a previously tombstoned secondary user (Platform only)","x-tableuser-key-class":"platform","security":[{"DeveloperApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnmergeRequest"}}}},"responses":{"200":{"description":"Unmerge result"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/link-app":{"post":{"tags":["users"],"summary":"Link an app registry id to a user by email (Platform only)","x-tableuser-key-class":"platform","security":[{"DeveloperApiKey":[]}],"responses":{"200":{"description":"Updated profile"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{userId}/roles":{"post":{"tags":["users"],"summary":"Assign platform-coarse role to user (Platform only)","description":"Assigns a global Logto role on the TableUser token. Response includes a note that this is not organization/project membership — consuming apps resolve grants by taid.","x-tableuser-key-class":"platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Assigned (includes note about platform-coarse semantics)"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{userId}/roles/{roleName}":{"delete":{"tags":["users"],"summary":"Revoke role from user (Platform only)","x-tableuser-key-class":"platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"roleName","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{userId}/profile":{"get":{"tags":["users"],"summary":"Unified profile (App keys strip identityAliases / mergedUserIds)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Unified profile"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sync":{"post":{"tags":["sync"],"summary":"App-sovereign user sync push (registry must be allowed)","x-tableuser-key-class":"app|platform","security":[{"DeveloperApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncPushRequest"}}}},"responses":{"200":{"description":"Sync summary"},"403":{"description":"Forbidden key class or registry not bound to this App key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/waitlist":{"post":{"tags":["waitlist"],"security":[],"summary":"Request developer access (no API key; IP rate limited)","x-tableuser-key-class":"app","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}}},"responses":{"201":{"description":"Waitlist entry created"},"429":{"description":"IP rate limited"}}}},"/v1/hooks/logto/user-created":{"post":{"tags":["hooks"],"security":[],"summary":"Logto User.Created / PostRegister webhook (HMAC)","responses":{"200":{"description":"TAID assigned if missing"}}}},"/admin/v1/overview":{"get":{"tags":["admin-console"],"summary":"Global totals (users, apps, recent signups, merge candidates)","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Overview stats from SQLite projection"}}}},"/admin/v1/apps":{"get":{"tags":["admin-console"],"summary":"All registered apps with per-app user counts","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"App registry rows"}}}},"/admin/v1/apps/{registryRef}/users":{"get":{"tags":["admin-console"],"summary":"Users linked to one app","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"registryRef","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Paginated app users"}}}},"/admin/v1/users":{"get":{"tags":["admin-console"],"summary":"Search identities (email / username / TAID)","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"search","in":"query","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Paginated identities"}}}},"/admin/v1/identity/{taid}":{"get":{"tags":["admin-console"],"summary":"Unified identity board for one TAID","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"taid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Identity + appLinks + merge status"}}}},"/admin/v1/merge-candidates":{"get":{"tags":["admin-console"],"summary":"Weak-signal merge queue (duplicate email/phone across TAIDs)","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Candidate groups"}}}},"/admin/v1/projection/refresh":{"post":{"tags":["admin-console"],"summary":"Trigger Logto → SQLite full projection","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Projection result"}}}},"/admin/v1/skills/status":{"get":{"tags":["admin-console"],"summary":"Latest tableuser agent-skill distribution status across marketplaces","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Skill channel statuses"}}}},"/admin/v1/keys":{"get":{"tags":["admin-keys"],"summary":"List developer API keys (includes kind, registries, 24h usage)","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Key metadata (no secrets)"}}},"post":{"tags":["admin-keys"],"summary":"Create developer API key (secret shown once)","security":[{"AdminAccessToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string"},"kind":{"type":"string","enum":["app","platform"],"default":"app"},"allowedRegistryIds":{"type":"array","items":{"type":"string"}},"maxApps":{"type":"integer","default":3},"waitlistId":{"type":"string"}}}}}},"responses":{"201":{"description":"Created with plaintext secret"}}}},"/admin/v1/keys/{id}/events":{"get":{"tags":["admin-keys"],"summary":"Usage audit events for a key","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}}],"responses":{"200":{"description":"Recent request events"}}}},"/admin/v1/keys/{id}":{"delete":{"tags":["admin-keys"],"summary":"Revoke developer API key","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"}}}},"/admin/v1/waitlist":{"get":{"tags":["admin-keys"],"summary":"List developer waitlist entries","security":[{"AdminAccessToken":[]}],"responses":{"200":{"description":"Waitlist entries"}}}},"/admin/v1/waitlist/{id}/approve":{"post":{"tags":["admin-keys"],"summary":"Approve waitlist entry and optionally mint an App key","security":[{"AdminAccessToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Approved (secret if minted)"}}}}},"externalDocs":{"url":"https://tableu.opcglobal.cn/developers/"}}