{"openapi":"3.1.0","info":{"title":"NYU Archway","description":"One NYU-issued key for every AI vendor NYU controls, with per-user, per-provider token quotas.","version":"0.1.0"},"paths":{"/v1/chat/completions":{"post":{"summary":"Chat Completions","operationId":"chat_completions_v1_chat_completions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Chat Completions V1 Chat Completions Post"}}}}}}},"/v1/embeddings":{"post":{"summary":"Embeddings","operationId":"embeddings_v1_embeddings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Embeddings V1 Embeddings Post"}}}}}}},"/v1/models":{"get":{"summary":"List Models","description":"Exactly what *this* key may call.\n\nFiltering by the key's scopes rather than listing the whole catalogue means\n`openai.models.list()` doubles as the answer to \"what do I have access to\",\nwhich is the first question every new user asks.","operationId":"list_models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Models V1 Models Get"}}}}}}},"/v1/messages":{"post":{"summary":"Messages","operationId":"messages_v1_messages_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Messages V1 Messages Post"}}}}}}},"/v1/messages/models":{"get":{"summary":"Anthropic Models","description":"Anthropic's `/v1/models` list shape, scoped to this key.\n\nMounted off `/v1/messages/` because `/v1/models` already belongs to the\nOpenAI surface and returning two different shapes on one path would break\nwhichever SDK asked second.","operationId":"anthropic_models_v1_messages_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Anthropic Models V1 Messages Models Get"}}}}}}},"/v1/messages/count_tokens":{"post":{"summary":"Count Tokens","description":"Estimate a prompt's size without spending any quota.\n\nThe SDK calls this before large requests. It costs nothing upstream, so it\nneither reserves nor meters - it is the one endpoint here that is free.","operationId":"count_tokens_v1_messages_count_tokens_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Count Tokens V1 Messages Count Tokens Post"}}}}}}},"/v1/providers":{"get":{"tags":["access"],"summary":"The third-party providers this key can reach","description":"Costs no tokens and counts against no quota.\n\nThe companion to `/v1/models`: same scoping, grouped the other way, plus the\ntwo things a model list cannot carry - how much of each vendor's allowance\nis left, and whether a vendor is behind it at all.","operationId":"list_providers_v1_providers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyProviderList"}}}}}}},"/v1/key":{"get":{"tags":["access"],"summary":"What this key is, and what it unlocks","description":"One call for everything an app needs when a visitor pastes a key in.\n\nA 200 here means the key authenticated, so this doubles as the validity\ncheck an app would otherwise perform by making a real completion request -\nwhich costs the visitor tokens to learn something free.","operationId":"describe_key_v1_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyInfo"}}}}}}},"/api/admin/users":{"get":{"tags":["admin"],"summary":"List users","operationId":"list_users_api_admin_users_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on email, NetID or full name.","title":"Q"},"description":"Substring match on email, NetID or full name."},{"name":"affiliation","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Affiliation"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},{"name":"quota_policy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_UserOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Create a user","operationId":"create_user_api_admin_users_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}":{"get":{"tags":["admin"],"summary":"Get one user","operationId":"get_user_api_admin_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Update a user","operationId":"update_user_api_admin_users__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/deprovision":{"post":{"tags":["admin"],"summary":"Deprovision a user","description":"Mark the user deprovisioned, revoke every key and destroy every session.\n\nOne transaction, on purpose: a partial deprovision that flips the status but\nleaves a key live is worse than none at all, because the console would then\nshow the account as closed while it is still spending money.","operationId":"deprovision_user_api_admin_users__user_id__deprovision_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprovisionResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/quota":{"get":{"tags":["admin"],"summary":"Quota status for one user","operationId":"user_quota_api_admin_users__user_id__quota_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"include_disabled","in":"query","required":false,"schema":{"type":"boolean","description":"Include providers that are currently disabled.","default":false,"title":"Include Disabled"},"description":"Include providers that are currently disabled."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserQuotaStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/keys":{"get":{"tags":["admin"],"summary":"List API keys","operationId":"list_keys_api_admin_keys_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on key name, prefix or owner email.","title":"Q"},"description":"Substring match on key name, prefix or owner email."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ApiKeyOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/keys":{"post":{"tags":["admin"],"summary":"Issue a key for a user","description":"The response contains the plaintext key. It is the only time it exists\noutside the caller's memory - we store a peppered HMAC and cannot recover\nit, so a lost key has to be replaced, not retrieved.","operationId":"create_key_api_admin_users__user_id__keys_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/keys/{key_id}":{"patch":{"tags":["admin"],"summary":"Update a key","operationId":"update_key_api_admin_keys__key_id__patch","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/keys/{key_id}/revoke":{"post":{"tags":["admin"],"summary":"Revoke a key","operationId":"revoke_key_api_admin_keys__key_id__revoke_post","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/providers":{"get":{"tags":["admin"],"summary":"List providers","operationId":"list_providers_api_admin_providers_get","parameters":[{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProviderOut"},"title":"Response List Providers Api Admin Providers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Create a provider","operationId":"create_provider_api_admin_providers_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/adapters":{"get":{"tags":["admin"],"summary":"List registered provider adapters","description":"The values `providers.adapter` will accept.\n\nRead from the registry, so a locally registered adapter appears here without\nthis file changing. The console's provider form is built from the same call.","operationId":"list_provider_adapters_api_admin_adapters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response List Provider Adapters Api Admin Adapters Get"}}}}}}},"/api/admin/providers/{provider_id}":{"get":{"tags":["admin"],"summary":"Get one provider","operationId":"get_provider_api_admin_providers__provider_id__get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Update a provider","operationId":"update_provider_api_admin_providers__provider_id__patch","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/providers/{provider_id}/enabled":{"post":{"tags":["admin"],"summary":"Enable or disable a provider","description":"The kill switch. Disabling stops all routing to the vendor immediately\nwithout destroying credentials, models or history.","operationId":"set_provider_enabled_api_admin_providers__provider_id__enabled_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderEnabledUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/providers/{provider_id}/credentials":{"get":{"tags":["admin"],"summary":"List a provider's credentials","operationId":"list_credentials_api_admin_providers__provider_id__credentials_get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredentialOut"},"title":"Response List Credentials Api Admin Providers  Provider Id  Credentials Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Add a credential","operationId":"create_credential_api_admin_providers__provider_id__credentials_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/credentials/{credential_id}/rotate":{"post":{"tags":["admin"],"summary":"Rotate a credential","description":"Add the replacement, then disable the incumbent - in that order, in one\ntransaction. There is therefore no instant at which the provider has no\nusable credential, which is the whole point of doing it as a single call.","operationId":"rotate_credential_api_admin_credentials__credential_id__rotate_post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialRotate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialRotateResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/credentials/{credential_id}":{"patch":{"tags":["admin"],"summary":"Update a credential","operationId":"update_credential_api_admin_credentials__credential_id__patch","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete a credential","description":"Deleting is not the same as disabling: the encrypted blob is gone. The\naudit row keeps the name and last4 so the deletion is still explicable.\n\nRefused while any key still pins this credential. `api_keys.allowed_credentials`\nis an ARRAY and so cannot carry a foreign key; without this check the row\nwould go and the pin would quietly become a dangling id, which\n`resolve_target` reads as \"not pinned here\" - the key would silently start\nbilling to whichever vendor key the weighted draw picked. Disable it instead\nif the intent is to drain it.","operationId":"delete_credential_api_admin_credentials__credential_id__delete","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/credentials/{credential_id}/disable":{"post":{"tags":["admin"],"summary":"Disable a credential","operationId":"disable_credential_api_admin_credentials__credential_id__disable_post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/providers/{provider_id}/test":{"post":{"tags":["admin"],"summary":"Verify a credential with a tiny live completion","description":"Answers \"does this key work?\" *before* students hit it.\n\nNever raises on an upstream failure - a failing vendor key is the expected\noutcome this endpoint exists to report, so it always returns 200 with\n`ok=false` and the reason. The decrypted secret goes straight into the\nrequest headers and is never written anywhere.","operationId":"test_provider_api_admin_providers__provider_id__test_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ProviderTestRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderTestResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/models":{"get":{"tags":["admin"],"summary":"List models","operationId":"list_models_api_admin_models_get","parameters":[{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on alias or upstream model name.","title":"Q"},"description":"Substring match on alias or upstream model name."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ModelOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Create a model","operationId":"create_model_api_admin_models_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/models/{model_id}":{"patch":{"tags":["admin"],"summary":"Update a model","operationId":"update_model_api_admin_models__model_id__patch","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete a model","description":"Prefer `enabled=false` over deletion: usage history references the model\nid, and a deleted row leaves reports labelling the id rather than the\nalias.","operationId":"delete_model_api_admin_models__model_id__delete","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/quota-policies":{"get":{"tags":["admin"],"summary":"List quota policies","operationId":"list_policies_api_admin_quota_policies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/QuotaPolicyOut"},"type":"array","title":"Response List Policies Api Admin Quota Policies Get"}}}}}},"post":{"tags":["admin"],"summary":"Create a quota policy","operationId":"create_policy_api_admin_quota_policies_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaPolicyCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaPolicyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/quota-policies/{policy_id}":{"get":{"tags":["admin"],"summary":"Get one policy","operationId":"get_policy_api_admin_quota_policies__policy_id__get","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaPolicyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Update a policy","operationId":"update_policy_api_admin_quota_policies__policy_id__patch","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaPolicyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaPolicyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete a policy","operationId":"delete_policy_api_admin_quota_policies__policy_id__delete","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/quota-policies/{policy_id}/assign":{"post":{"tags":["admin"],"summary":"Assign a policy in bulk","operationId":"assign_policy_api_admin_quota_policies__policy_id__assign_post","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyAssignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/overrides":{"get":{"tags":["admin"],"summary":"List quota overrides","operationId":"list_overrides_api_admin_overrides_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","description":"Hide overrides whose expiry has passed.","default":false,"title":"Active Only"},"description":"Hide overrides whose expiry has passed."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_OverrideOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Create a quota override","operationId":"create_override_api_admin_overrides_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/overrides/{override_id}":{"patch":{"tags":["admin"],"summary":"Update an override","operationId":"update_override_api_admin_overrides__override_id__patch","parameters":[{"name":"override_id","in":"path","required":true,"schema":{"type":"string","title":"Override Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete an override","operationId":"delete_override_api_admin_overrides__override_id__delete","parameters":[{"name":"override_id","in":"path","required":true,"schema":{"type":"string","title":"Override Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usage/summary":{"get":{"tags":["admin"],"summary":"Usage totals by provider and model","operationId":"usage_summary_api_admin_usage_summary_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive UTC start date. Defaults to 30 days ago.","title":"Start"},"description":"Inclusive UTC start date. Defaults to 30 days ago."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive UTC end date. Defaults to today.","title":"End"},"description":"Inclusive UTC end date. Defaults to today."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usage/timeseries":{"get":{"tags":["admin"],"summary":"Daily usage buckets","operationId":"usage_timeseries_api_admin_usage_timeseries_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},{"name":"group_by","in":"query","required":false,"schema":{"enum":["none","provider"],"type":"string","default":"none","title":"Group By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageTimeseries"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usage/top-users":{"get":{"tags":["admin"],"summary":"Heaviest users in a window","operationId":"usage_top_users_api_admin_usage_top_users_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End"}},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},{"name":"metric","in":"query","required":false,"schema":{"enum":["tokens","requests","cost"],"type":"string","default":"tokens","title":"Metric"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TopUserRow"},"title":"Response Usage Top Users Api Admin Usage Top Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usage/events":{"get":{"tags":["admin"],"summary":"Raw request log","operationId":"usage_events_api_admin_usage_events_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id"}},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},{"name":"public_alias","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact model alias.","title":"Public Alias"},"description":"Exact model alias."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on user email or request id.","title":"Q"},"description":"Substring match on user email or request id."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_UsageEventOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/key-requests":{"get":{"tags":["admin"],"summary":"List key requests","operationId":"list_key_requests_api_admin_key_requests_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"`pending`, `approved`, `denied` or `all`.","default":"pending","title":"Status"},"description":"`pending`, `approved`, `denied` or `all`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_KeyRequestOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/key-requests/{request_id}/approve":{"post":{"tags":["admin"],"summary":"Approve a key request and issue the key","description":"Approval and issuance are one transaction: a request marked approved\nwith no key attached is an unanswerable support ticket.","operationId":"approve_key_request_api_admin_key_requests__request_id__approve_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestApprove"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestApproved"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/key-requests/{request_id}/deny":{"post":{"tags":["admin"],"summary":"Deny a key request","operationId":"deny_key_request_api_admin_key_requests__request_id__deny_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestDeny"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/audit":{"get":{"tags":["admin"],"summary":"Read the audit log","operationId":"list_audit_api_admin_audit_get","parameters":[{"name":"actor_user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor User Id"}},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact action, e.g. `key.revoke`.","title":"Action"},"description":"Exact action, e.g. `key.revoke`."},{"name":"action_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Action family, e.g. `credential`.","title":"Action Prefix"},"description":"Action family, e.g. `credential`."},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"}},{"name":"entity_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on actor email or summary.","title":"Q"},"description":"Substring match on actor email or summary."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AuditLogOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portal/me":{"get":{"tags":["portal"],"summary":"Who am I, and what may I do","operationId":"get_me_api_portal_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalMe"}}}}}}},"/api/portal/keys":{"get":{"tags":["portal"],"summary":"My API keys","operationId":"list_my_keys_api_portal_keys_get","parameters":[{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Revoked"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyOut"},"title":"Response List My Keys Api Portal Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["portal"],"summary":"Create one of my own keys","description":"Gated on self-service issuance being enabled.\n\nWhen it is not, the 403 carries the actual next step rather than a bare\ndenial - a student who cannot get a key needs to be told how to ask for\none, not that they are forbidden.","operationId":"create_my_key_api_portal_keys_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portal/keys/{key_id}":{"delete":{"tags":["portal"],"summary":"Revoke one of my keys","description":"Scoped by `user_id` in the WHERE clause, not checked after loading: a key\nbelonging to someone else is simply not found, which leaks nothing about\nwhether that id exists.","operationId":"revoke_my_key_api_portal_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portal/quota":{"get":{"tags":["portal"],"summary":"My per-provider token quota","operationId":"my_quota_api_portal_quota_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalQuota"}}}}}}},"/api/portal/usage":{"get":{"tags":["portal"],"summary":"My usage","operationId":"my_usage_api_portal_usage_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive UTC start date. Defaults to 30 days ago.","title":"Start"},"description":"Inclusive UTC start date. Defaults to 30 days ago."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive UTC end date. Defaults to today.","title":"End"},"description":"Inclusive UTC end date. Defaults to today."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalUsage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portal/usage/events":{"get":{"tags":["portal"],"summary":"My request log","operationId":"my_usage_events_api_portal_usage_events_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End"}},{"name":"provider_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_UsageEventOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portal/models":{"get":{"tags":["portal"],"summary":"The models I can actually call, grouped by provider","description":"Not the whole catalogue - the *effective* catalogue.\n\nA model is listed only if the provider is enabled, the model is enabled,\nand at least one of the user's active keys can reach it. Showing a model a\nuser's key would reject is the single most confusing thing this endpoint\ncould do.","operationId":"my_models_api_portal_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PortalProviderModels"},"type":"array","title":"Response My Models Api Portal Models Get"}}}}}}},"/api/portal/nyu-units":{"get":{"tags":["portal"],"summary":"NYU schools and offices","description":"The vocabulary `POST /key-requests` accepts for `department`.\n\nServed rather than documented as a literal so a scripted caller and the\nportal form can never be offering different lists - `nyugw.org` is the one\nplace either reads. Grouped the way the form groups it, because a flat\nforty-item list is not a choice anybody can make.","operationId":"nyu_units_api_portal_nyu_units_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/NyuUnitGroup"},"type":"array","title":"Response Nyu Units Api Portal Nyu Units Get"}}}}}}},"/api/portal/key-requests":{"get":{"tags":["portal"],"summary":"My key requests","operationId":"my_key_requests_api_portal_key_requests_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/KeyRequestOut"},"type":"array","title":"Response My Key Requests Api Portal Key Requests Get"}}}}}},"post":{"tags":["portal"],"summary":"Ask the gateway team for a key","description":"The path for when self-serve issuance is off - which is the launch\ndefault. One open request per user at a time, so the internal team's queue\nreflects people rather than clicks.","operationId":"create_key_request_api_portal_key_requests_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/posture":{"get":{"tags":["security"],"summary":"Whole-gateway security posture in one call","description":"The security dashboard's single data source (CSF DE.AE-06, RS.MA-01).\n\nOne round trip, because a console that issues nine requests to render is a\nconsole nobody opens at 02:00. Read-only: it writes no event, so opening the\ndashboard does not pollute the telemetry it is displaying.","operationId":"get_posture_api_security_posture_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityPosture"}}}}}}},"/api/security/events":{"get":{"tags":["security"],"summary":"Search security telemetry","description":"Paginated, filtered security event search.\n\nUnbounded by construction is not an option: an authentication flood\nproduces precisely the largest table here, which is the moment a\n`SELECT *` would take the console down with it.","operationId":"list_events_api_security_events_get","parameters":[{"name":"event_type","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter to these event types.","title":"Event Type"},"description":"Filter to these event types."},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter to these severities.","title":"Severity"},"description":"Filter to these severities."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events concerning this user.","title":"User Id"},"description":"Events concerning this user."},{"name":"actor_email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events recorded against this email.","title":"Actor Email"},"description":"Events recorded against this email."},{"name":"client_ip","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events from this source address.","title":"Client Ip"},"description":"Events from this source address."},{"name":"request_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Correlate with one request.","title":"Request Id"},"description":"Correlate with one request."},{"name":"target_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events about this object id.","title":"Target Id"},"description":"Events about this object id."},{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events involving this NYU key.","title":"Api Key Id"},"description":"Events involving this NYU key."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only events at or after this time.","title":"Since"},"description":"Only events at or after this time."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only events at or before this time.","title":"Until"},"description":"Only events at or before this time."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Substring match on the message.","title":"Q"},"description":"Substring match on the message."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SecurityEventOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/events/summary":{"get":{"tags":["security"],"summary":"Bucketed event counts for charting","description":"Per-type and over-time aggregates.\n\nThe timeline granularity is coerced down to `day` on windows longer than two\nweeks. An hourly series over a year is 8,760 points that no chart can render\nand no operator can read, and building it is a self-inflicted denial of\nservice on the console.","operationId":"events_summary_api_security_events_summary_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Window start. Defaults to 7 days ago.","title":"Since"},"description":"Window start. Defaults to 7 days ago."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Window end. Defaults to now.","title":"Until"},"description":"Window end. Defaults to now."},{"name":"interval","in":"query","required":false,"schema":{"enum":["hour","day"],"type":"string","description":"Timeline granularity.","default":"hour","title":"Interval"},"description":"Timeline granularity."},{"name":"types","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many event types to return, highest count first.","default":20,"title":"Types"},"description":"How many event types to return, highest count first."},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Restrict to these severities.","title":"Severity"},"description":"Restrict to these severities."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one user.","title":"User Id"},"description":"Restrict to one user."},{"name":"client_ip","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one source address.","title":"Client Ip"},"description":"Restrict to one source address."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityEventSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/anomalies":{"get":{"tags":["security"],"summary":"Triage queue","description":"Open signals first, worst severity first, oldest-seen first within that.\n\nThe default filter hides resolved signals, because the question this page\nanswers is \"what is currently worrying us\", not \"what has ever worried us\".","operationId":"list_anomalies_api_security_anomalies_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by status. Defaults to open + acknowledged.","title":"Status"},"description":"Filter by status. Defaults to open + acknowledged."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by detection rule.","title":"Kind"},"description":"Filter by detection rule."},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by severity.","title":"Severity"},"description":"Filter by severity."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Signals implicating this user.","title":"User Id"},"description":"Signals implicating this user."},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Signals implicating this provider.","title":"Provider Id"},"description":"Signals implicating this provider."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Last seen at or after this time.","title":"Since"},"description":"Last seen at or after this time."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Last seen at or before this time.","title":"Until"},"description":"Last seen at or before this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AnomalyOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/anomalies/{signal_id}/acknowledge":{"post":{"tags":["security"],"summary":"Mark a signal as seen","description":"Acknowledgement records that a human looked, and who. It does not close\nthe signal - `unacknowledged` on the posture dashboard is the number that\nshould be zero, and this is the only thing that moves it.","operationId":"acknowledge_anomaly_api_security_anomalies__signal_id__acknowledge_post","parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnomalyAcknowledgeRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnomalyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/anomalies/{signal_id}/resolve":{"post":{"tags":["security"],"summary":"Close a signal with a written outcome","description":"Closing requires a note (422 without one).\n\n`false_positive` is a distinct outcome rather than a flavour of resolved,\nbecause the count of false positives per rule is the only honest input to a\nthreshold review - and a detector nobody tunes is a detector everybody\nlearns to ignore (CSF DE.AE-08, ID.IM-01).","operationId":"resolve_anomaly_api_security_anomalies__signal_id__resolve_post","parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnomalyResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnomalyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/anomalies/run":{"post":{"tags":["security"],"summary":"Run detection on demand","description":"Trigger a detection sweep without waiting for the schedule.\n\nUsed two ways: to confirm a rule fires after tuning it, and during an\nincident when waiting for the next scheduled pass is not an option. The\nsweep's effect is measured by observing which signals moved, so this\nendpoint does not depend on the detector's return shape.","operationId":"run_detection_api_security_anomalies_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectionRunResult"}}}}}}},"/api/security/contain/key/{key_id}":{"post":{"tags":["security"],"summary":"Revoke a compromised NYU key","description":"Kill one key (CSF RS.MI-01).\n\nA leaked NYU key is free access to NYU's paid vendor accounts, so this is\nthe most-used control in the console and it is one click plus a sentence.\nRevocation takes effect on the next request: authentication is a single\nindexed lookup against `api_keys.status`, with no cached allow decision to\noutlive it.\n\nThis is the one containment whose revert restores the target, because\n\"wrong key id at 02:00\" is exactly the mistake revert exists for.","operationId":"contain_key_api_security_contain_key__key_id__post","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/contain/user/{user_id}":{"post":{"tags":["security"],"summary":"Suspend an account and cut its keys","description":"Suspend an account (CSF RS.MI-01).\n\nKeys are revoked alongside the suspension by default, because the proxy\nsurface authenticates on the key: suspending the account while leaving its\nkeys live contains nothing at all.\n\nThose key revocations are **not** undone by a revert. If the account was\nsuspended because it was compromised, its previously-live keys were exposed;\nrestoring them on the way back is the wrong answer. Reverting restores\naccount access, and the user is issued new keys.","operationId":"contain_user_api_security_contain_user__user_id__post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainUserRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/contain/provider/{provider_id}":{"post":{"tags":["security"],"summary":"Stop routing to a vendor","description":"Disable a whole vendor (CSF RS.MI-01, GV.SC-10).\n\nTwo escalation levels, deliberately separated: disabling the provider stops\nNYU traffic reaching the vendor, while `disable_credentials` additionally\ntakes the stored upstream keys out of service. The second is for suspected\n*credential* compromise - threat #3, the unbounded unattributed bill - and\nit is not the default, because it converts a routing decision into a\nrotation the internal team then has to unwind.","operationId":"contain_provider_api_security_contain_provider__provider_id__post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainProviderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/freeze":{"post":{"tags":["security"],"summary":"Break glass: stop all traffic","description":"Halt every proxied request, gateway-wide (CSF RS.MI-01, RS.MI-02).\n\nThe control of last resort: it stops all NYU spend against every vendor at\nonce, which is the only answer when the blast radius is unknown. Idempotent\non purpose - a break-glass button that errors when pressed twice is a button\npeople hesitate over, and hesitation is the thing this exists to remove.","operationId":"freeze_traffic_api_security_freeze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreezeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/unfreeze":{"post":{"tags":["security"],"summary":"Lift the global freeze","description":"Resume traffic. Also requires a written reason: restoring service during\na live incident is at least as consequential as cutting it.","operationId":"unfreeze_traffic_api_security_unfreeze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/containment/{action_id}/revert":{"post":{"tags":["security"],"summary":"Lift a containment action","description":"Undo a containment action, restoring exactly what it changed.\n\nThe restoration is driven by the before/after record written at apply time,\nnot by a guess at what \"normal\" looks like, so reverting cannot accidentally\nre-enable a provider that was already disabled for an unrelated reason. When\nthat record is missing - an action written by an older build - the revert\nfalls back to a deterministic minimum and says so in the response rather\nthan pretending to precision it does not have.\n\nCollateral key revocations from a user containment are never restored; see\n`POST /contain/user/{id}`.","operationId":"revert_containment_api_security_containment__action_id__revert_post","parameters":[{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/containment":{"get":{"tags":["security"],"summary":"Containment history","description":"Every emergency action ever taken, reverted or not.\n\nReverted actions stay in this list permanently. \"We froze traffic at 02:14\nbecause X, and lifted it at 03:40 because Y\" is the sentence an incident\nreview is built from, and it has to survive the undo.","operationId":"list_containment_api_security_containment_get","parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by scope.","title":"Scope"},"description":"Filter by scope."},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"True for actions still in force, false for lifted ones.","title":"Active"},"description":"True for actions still in force, false for lifted ones."},{"name":"target_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actions against this object.","title":"Target Id"},"description":"Actions against this object."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Applied at or after this time.","title":"Since"},"description":"Applied at or after this time."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Applied at or before this time.","title":"Until"},"description":"Applied at or before this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ContainmentOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/containment/{action_id}":{"get":{"tags":["security"],"summary":"One containment action","operationId":"get_containment_api_security_containment__action_id__get","parameters":[{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContainmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/locks":{"get":{"tags":["security"],"summary":"Account lockouts","description":"Who is locked out, and why.\n\nLocks against addresses with no matching account are shown too: a run of\nthem is somebody guessing at NYU addresses, which is a detection signal\nrather than a support ticket.","operationId":"list_locks_api_security_locks_get","parameters":[{"name":"active","in":"query","required":false,"schema":{"type":"boolean","description":"Only locks in force right now.","default":true,"title":"Active"},"description":"Only locks in force right now."},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"description":"Filter to one address.","title":"Email"},"description":"Filter to one address."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Locked at or after this time.","title":"Since"},"description":"Locked at or after this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AccountLockOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/locks/{lock_id}":{"delete":{"tags":["security"],"summary":"Clear a lockout","description":"Lift an automated lockout (CSF PR.AA-03, RS.MI-01).\n\nThe row is marked cleared rather than deleted, so the fact that an account\nwas locked - and that an administrator unlocked it - both survive.","operationId":"clear_lock_api_security_locks__lock_id__delete","parameters":[{"name":"lock_id","in":"path","required":true,"schema":{"type":"string","title":"Lock Id"}},{"name":"reason","in":"query","required":true,"schema":{"type":"string","minLength":4,"maxLength":2000,"description":"Why this lockout is being lifted. Required: clearing a lock during a live brute-force is exactly the action that needs explaining.","title":"Reason"},"description":"Why this lockout is being lifted. Required: clearing a lock during a live brute-force is exactly the action that needs explaining."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LockClearResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/mfa/enroll":{"post":{"tags":["security"],"summary":"Start TOTP enrolment","description":"Begin second-factor enrolment for the calling operator (CSF PR.AA-03).\n\nSelf-service only. An administrator who could enrol a factor *for* another\naccount would hold that account's factor, which turns MFA from an access\ncontrol into an impersonation tool - threat #6, insider misuse.\n\nThe response carries the provisioning URI and the recovery codes, once. The\nURI necessarily contains the seed; it is never logged, never written into\nthe security event, and cannot be fetched again. A lost enrolment is\nrestarted, not recovered.","operationId":"enroll_mfa_api_security_mfa_enroll_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaEnrollRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaEnrollResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/mfa/confirm":{"post":{"tags":["security"],"summary":"Activate TOTP enrolment","description":"Prove possession before the factor counts.\n\nAn unconfirmed enrolment is never treated as coverage anywhere in this API,\nso an operator cannot appear compliant on the posture dashboard while\nholding a seed they never loaded into an authenticator.","operationId":"confirm_mfa_api_security_mfa_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaConfirmResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/mfa":{"delete":{"tags":["security"],"summary":"Remove a second factor","description":"Remove a TOTP factor (CSF PR.AA-03, PR.AA-05, GV.RR-02).\n\nTwo paths, and the separation is the control:\n\n* **Self-service** requires a current code or an unused recovery code.\n  Without that, anyone holding a stolen session cookie could quietly strip\n  the factor that would otherwise have stopped them - which would make MFA\n  protection that evaporates precisely when it is needed.\n* **Owner reset** lets the gateway owner clear another operator's factor for\n  the lost-phone case. Restricted to the `owner` role, never usable on your\n  own account, and always recorded at high severity, so no single\n  administrator can quietly de-MFA a peer and then act as them.","operationId":"disable_mfa_api_security_mfa_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaDisableRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcknowledgedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/mfa/status":{"get":{"tags":["security"],"summary":"Second-factor status","description":"Enrolment state, carrying no seed, no URI and no recovery codes - only\ncounts and timestamps. There is no endpoint anywhere in this API that\nreturns a TOTP secret after enrolment.","operationId":"mfa_status_api_security_mfa_status_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Inspect another operator's enrolment. Omit for your own.","title":"User Id"},"description":"Inspect another operator's enrolment. Omit for your own."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaStatusOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/vendor-assessments":{"get":{"tags":["security"],"summary":"List vendor assessments","operationId":"list_vendor_assessments_api_security_vendor_assessments_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by assessment status.","title":"Status"},"description":"Filter by assessment status."},{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one provider.","title":"Provider Id"},"description":"Filter to one provider."},{"name":"overdue","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only overdue, or only current, reviews.","title":"Overdue"},"description":"Only overdue, or only current, reviews."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_VendorAssessmentOut_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["security"],"summary":"Record a vendor assessment","description":"Record the due-diligence position for one vendor (CSF GV.SC-04).\n\nThe gateway's entire purpose is routing NYU traffic to external companies,\nwhich makes those companies a supply chain. This row is the university's\nwritten answer to \"may student work go to this vendor\" - one per provider,\nenforced by a unique constraint, because two contradictory answers are worse\nthan none.","operationId":"create_vendor_assessment_api_security_vendor_assessments_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorAssessmentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorAssessmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/vendor-assessments/{assessment_id}":{"get":{"tags":["security"],"summary":"One vendor assessment","operationId":"get_vendor_assessment_api_security_vendor_assessments__assessment_id__get","parameters":[{"name":"assessment_id","in":"path","required":true,"schema":{"type":"string","title":"Assessment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorAssessmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["security"],"summary":"Update a vendor assessment","description":"Amend an assessment (CSF GV.SC-07).\n\nRaising the approved data classification, or moving a vendor to `approved`,\nis recorded at high severity. Those two edits are how student data becomes\npermitted to leave the university, and they should be as visible in the\nevent stream as a containment action.","operationId":"update_vendor_assessment_api_security_vendor_assessments__assessment_id__patch","parameters":[{"name":"assessment_id","in":"path","required":true,"schema":{"type":"string","title":"Assessment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorAssessmentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorAssessmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["security"],"summary":"Delete a vendor assessment","description":"Remove an assessment.\n\nRecorded at medium severity because deleting a due-diligence record on a\nvendor the gateway still routes to returns that vendor to the \"never\nassessed\" column - it does not make the risk go away, and the event is the\nonly thing that will remember it was ever answered.","operationId":"delete_vendor_assessment_api_security_vendor_assessments__assessment_id__delete","parameters":[{"name":"assessment_id","in":"path","required":true,"schema":{"type":"string","title":"Assessment Id"}},{"name":"reason","in":"query","required":true,"schema":{"type":"string","minLength":4,"maxLength":2000,"description":"Why this record is being removed.","title":"Reason"},"description":"Why this record is being removed."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcknowledgedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/vendor-risk":{"get":{"tags":["security"],"summary":"Supply-chain risk view","description":"Which vendors are approved for which data, and which reviews have lapsed\n(CSF GV.SC-04, GV.SC-07, GV.SC-09).\n\nEvery provider appears, assessed or not, because the providers with no row\nare the finding. Ordered worst-first so the answer is at the top.","operationId":"vendor_risk_api_security_vendor_risk_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRiskSummary"}}}}}}},"/api/security/retention":{"get":{"tags":["security"],"summary":"Retention policies","description":"The declared data lifecycle (CSF PR.DS-10, ID.AM-08).\n\nBounded rather than paginated: the dataset list is a fixed handful, and a\nreviewer wants all of it on one page.","operationId":"list_retention_api_security_retention_get","parameters":[{"name":"estimate","in":"query","required":false,"schema":{"type":"boolean","description":"Also count rows currently older than each window. Off by default: it is a full count over the largest tables in the database.","default":false,"title":"Estimate"},"description":"Also count rows currently older than each window. Off by default: it is a full count over the largest tables in the database."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RetentionPolicyOut"},"title":"Response List Retention Api Security Retention Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/retention/{dataset}":{"put":{"tags":["security"],"summary":"Change a retention window","description":"Change how long a dataset is kept (CSF PR.DS-10).\n\nShortening a window, or disabling a policy, is recorded at high severity and\nrefused outright below the floor for evidence-bearing datasets. The event\ndescribing the change is written before the change takes effect and is\nitself subject to the `security_events` floor, so shortening retention\ncannot erase the record of shortening retention.","operationId":"update_retention_api_security_retention__dataset__put","parameters":[{"name":"dataset","in":"path","required":true,"schema":{"type":"string","title":"Dataset"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetentionPolicyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetentionPolicyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/retention/purge":{"post":{"tags":["security"],"summary":"Run the retention purge now","description":"Age out data past its declared window (CSF PR.DS-10).\n\n`dry_run` counts without deleting, and is the safe first click. The run\nwrites a `data.retention.purge` event recording exactly how many rows went\nfrom each dataset, so deletion is auditable - a purge nobody can account for\nafterwards is indistinguishable from evidence destruction.","operationId":"run_retention_purge_api_security_retention_purge_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetentionPurgeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetentionPurgeResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/users/{user_id}/export":{"get":{"tags":["security"],"summary":"Everything held about one person","description":"Subject data export (CSF PR.DS-10, GV.OC-03).\n\nAnswers a data-subject request in one call, and states what is *not* held as\nexplicitly as what is. The absence of prompt and response content is the\nsingle most important fact in this document; a reviewer should not have to\ninfer it from an empty field.\n\nUsage is aggregated rather than itemised. A per-request list of everything\nsomeone asked a model, timestamped, is surveillance - and the gateway is not\ngoing to build it in the name of transparency.","operationId":"export_user_data_api_security_users__user_id__export_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":3650,"minimum":1,"description":"How far back to aggregate usage.","default":365,"title":"Days"},"description":"How far back to aggregate usage."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDataExport"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/security/users/{user_id}/purge":{"post":{"tags":["security"],"summary":"Erase a person from the gateway","description":"Erase a data subject (CSF PR.DS-10, GV.OC-03).\n\nThe account row is pseudonymised rather than deleted, deliberately.\n`usage_daily` and `quota_counters` cascade from `users`; deleting the row\nwould destroy the rollups NYU reconciles vendor invoices against and would\nsilently rewrite spend history. Identifiers are replaced with a tombstone\nand every credential is destroyed, so the consumption totals survive\nattached to a subject nobody can re-identify.\n\nSecurity telemetry is kept and de-identified rather than removed. An\nerasure request that deletes the evidence of an incident is an erasure\nrequest an attacker would submit.","operationId":"purge_user_data_api_security_users__user_id__purge_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPurgeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPurgeResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security":{"get":{"summary":"Security Posture","operationId":"security_posture_admin_security_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin/security/events":{"get":{"summary":"Security Events","operationId":"security_events_admin_security_events_get","parameters":[{"name":"severity","in":"query","required":false,"schema":{"type":"string","default":"","title":"Severity"}},{"name":"event_type","in":"query","required":false,"schema":{"type":"string","default":"","title":"Event Type"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/anomalies":{"get":{"summary":"Security Anomalies","operationId":"security_anomalies_admin_security_anomalies_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"open","title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/anomalies/{signal_id}/{action}":{"post":{"summary":"Anomaly Triage","operationId":"anomaly_triage_admin_security_anomalies__signal_id___action__post","parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}},{"name":"action","in":"path","required":true,"schema":{"type":"string","title":"Action"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_anomaly_triage_admin_security_anomalies__signal_id___action__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/containment":{"get":{"summary":"Security Containment","operationId":"security_containment_admin_security_containment_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin/security/freeze":{"post":{"summary":"Freeze","operationId":"freeze_admin_security_freeze_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_freeze_admin_security_freeze_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/unfreeze":{"post":{"summary":"Do Unfreeze","operationId":"do_unfreeze_admin_security_unfreeze_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_do_unfreeze_admin_security_unfreeze_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/containment/{action_id}/revert":{"post":{"summary":"Revert Containment","operationId":"revert_containment_admin_security_containment__action_id__revert_post","parameters":[{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_revert_containment_admin_security_containment__action_id__revert_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/locks/{lock_id}/clear":{"post":{"summary":"Clear Lock","operationId":"clear_lock_admin_security_locks__lock_id__clear_post","parameters":[{"name":"lock_id","in":"path","required":true,"schema":{"type":"string","title":"Lock Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_clear_lock_admin_security_locks__lock_id__clear_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/vendor-risk":{"get":{"summary":"Vendor Risk","operationId":"vendor_risk_admin_security_vendor_risk_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"AccountLockOut":{"properties":{"id":{"type":"string","title":"Id","description":"Lock id."},"email":{"type":"string","title":"Email","description":"Account the lock applies to."},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Matching user, when the locked email corresponds to a real account. Null means someone is guessing at an address that does not exist here."},"reason":{"type":"string","title":"Reason","description":"Why the lock was placed."},"locked_at":{"type":"string","format":"date-time","title":"Locked At","description":"When the lock was placed (UTC)."},"unlock_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Unlock At","description":"When it expires by itself. Null means indefinite - an administrator must clear it."},"cleared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cleared At","description":"When an administrator cleared it."},"cleared_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who cleared it."},"active":{"type":"boolean","title":"Active","description":"True while the lock is still in force right now."},"indefinite":{"type":"boolean","title":"Indefinite","description":"True when the lock has no automatic expiry."}},"type":"object","required":["id","email","reason","locked_at","active","indefinite"],"title":"AccountLockOut","description":"An automated brute-force lockout (CSF PR.AA-03, RS.MI-01)."},"AcknowledgedResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","description":"True when the action was applied.","default":true},"message":{"type":"string","title":"Message","description":"What happened, in one sentence."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written for this action, for audit follow-up."}},"type":"object","required":["message"],"title":"AcknowledgedResponse","description":"Generic acknowledgement for an action with no richer result."},"ActorRef":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"User id of the actor, if known."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Actor email as recorded at the time of the action."}},"type":"object","title":"ActorRef","description":"Who did something, denormalised so the row stays readable after the\nactor is deprovisioned and the foreign key nulls out."},"AnomalyAcknowledgeRequest":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note","description":"Optional triage note recorded with the acknowledgement."}},"type":"object","title":"AnomalyAcknowledgeRequest","description":"Acknowledging says \"a human has seen this\", nothing more. A note is\noptional here and mandatory on resolve, because claiming something is\n*finished* is the assertion that needs justifying."},"AnomalyOut":{"properties":{"id":{"type":"string","title":"Id","description":"Signal id."},"kind":{"type":"string","title":"Kind","description":"Detection rule that fired, from `ANOMALY_KINDS`."},"severity":{"type":"string","title":"Severity","description":"Triage severity."},"status":{"type":"string","title":"Status","description":"open, acknowledged, resolved or false_positive."},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Implicated user, if the rule identifies one."},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email","description":"Email of the implicated user, resolved for display."},"api_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id","description":"Implicated NYU key id, if any."},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id","description":"Implicated provider id, if any."},"title":{"type":"string","title":"Title","description":"One-line statement of what was detected."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Longer explanation for the analyst."},"evidence":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Evidence","description":"The numbers that triggered the rule, so an analyst can judge without re-querying. Redacted at the API boundary like every other free-form payload."},"dedupe_key":{"type":"string","title":"Dedupe Key","description":"Stable handle that makes a repeatedly-firing rule update one row instead of flooding the console."},"occurrences":{"type":"integer","title":"Occurrences","description":"How many times this signal has re-fired."},"first_seen_at":{"type":"string","format":"date-time","title":"First Seen At","description":"First occurrence (UTC)."},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At","description":"Most recent occurrence (UTC)."},"acknowledged_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who acknowledged it, if anyone."},"acknowledged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Acknowledged At","description":"When it was acknowledged."},"resolution_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Note","description":"Written outcome recorded when the signal was resolved."},"age_hours":{"type":"number","title":"Age Hours","description":"Hours since the signal was last seen. Drives the console's staleness sort."}},"type":"object","required":["id","kind","severity","status","title","dedupe_key","occurrences","first_seen_at","last_seen_at","age_hours"],"title":"AnomalyOut","description":"A detection that warrants human attention (CSF DE.AE-02, DE.AE-06)."},"AnomalyPosture":{"properties":{"open_total":{"type":"integer","title":"Open Total","description":"Signals still open."},"by_severity":{"$ref":"#/components/schemas/SeverityBreakdown","description":"Open signals per severity."},"acknowledged_open":{"type":"integer","title":"Acknowledged Open","description":"Open signals a human has already seen."},"unacknowledged":{"type":"integer","title":"Unacknowledged","description":"Open signals nobody has looked at. This is the number that should be zero."},"oldest_open_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Oldest Open At","description":"Last-seen time of the longest-standing open signal."},"detected_24h":{"type":"integer","title":"Detected 24H","description":"Signals that fired in the last 24 hours."}},"type":"object","required":["open_total","by_severity","acknowledged_open","unacknowledged","detected_24h"],"title":"AnomalyPosture"},"AnomalyResolveRequest":{"properties":{"note":{"type":"string","maxLength":2000,"minLength":8,"title":"Note","description":"Why this signal is closed - what was found, and what was done about it. Minimum 8 characters."},"status":{"type":"string","enum":["resolved","false_positive"],"title":"Status","description":"`false_positive` records that the rule fired wrongly, which is the input a threshold review needs (CSF DE.AE-08, ID.IM-01).","default":"resolved"}},"type":"object","required":["note"],"title":"AnomalyResolveRequest","description":"Closing a signal. The note is required: a resolved anomaly with no\nexplanation is indistinguishable from one that was clicked away."},"ApiKeyCreate":{"properties":{"name":{"type":"string","maxLength":120,"title":"Name","description":"Label shown in the portal.","default":"default"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Convenience alternative to `expires_at`."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers","description":"Restrict to these provider slugs. Empty = unrestricted."},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models","description":"Restrict to these public aliases. Empty = unrestricted."},"allowed_credentials":{"items":{"type":"string"},"type":"array","title":"Allowed Credentials","description":"Pin calls to these vendor credential ids. Empty = any active credential on the provider. Rejected if a pinned credential's provider is not in `allowed_providers`."}},"type":"object","title":"ApiKeyCreate"},"ApiKeyCreated":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix","description":"Display-safe fragment. The full key is unrecoverable."},"status":{"type":"string","title":"Status"},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers","description":"Provider slugs. Empty means every enabled provider."},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models","description":"Public model aliases. Empty means every enabled model."},"allowed_credentials":{"items":{"type":"string"},"type":"array","title":"Allowed Credentials","description":"Vendor credential ids this key's calls must be served by. Empty means any active credential. Scoped per provider: pinning an OpenAI credential does not restrict which Anthropic credential serves an Anthropic call."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"key":{"type":"string","title":"Key","description":"The plaintext key. Shown exactly once - it is stored only as a hash."}},"type":"object","required":["id","user_id","name","key_prefix","status","created_at","updated_at","key"],"title":"ApiKeyCreated","description":"Returned once, at creation. `key` never appears in any other response."},"ApiKeyOut":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix","description":"Display-safe fragment. The full key is unrecoverable."},"status":{"type":"string","title":"Status"},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers","description":"Provider slugs. Empty means every enabled provider."},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models","description":"Public model aliases. Empty means every enabled model."},"allowed_credentials":{"items":{"type":"string"},"type":"array","title":"Allowed Credentials","description":"Vendor credential ids this key's calls must be served by. Empty means any active credential. Scoped per provider: pinning an OpenAI credential does not restrict which Anthropic credential serves an Anthropic call."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","name","key_prefix","status","created_at","updated_at"],"title":"ApiKeyOut"},"ApiKeyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Name"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Send `null` explicitly to make the key non-expiring."},"allowed_providers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Providers","description":"Send `[]` to lift all provider restrictions."},"allowed_models":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Models"},"allowed_credentials":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Credentials","description":"Send `[]` to unpin the key from specific vendor credentials."}},"type":"object","title":"ApiKeyUpdate"},"AuditLogOut":{"properties":{"id":{"type":"string","title":"Id"},"actor_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor User Id"},"actor_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Email"},"action":{"type":"string","title":"Action"},"entity_type":{"type":"string","title":"Entity Type"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"before":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Before"},"after":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"After"},"client_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Ip"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","action","entity_type","created_at"],"title":"AuditLogOut"},"AuthPosture":{"properties":{"failed_logins_24h":{"type":"integer","title":"Failed Logins 24H","description":"Failed sign-in attempts in the last 24 hours."},"successful_logins_24h":{"type":"integer","title":"Successful Logins 24H","description":"Successful sign-ins in the last 24 hours."},"failed_distinct_emails_24h":{"type":"integer","title":"Failed Distinct Emails 24H","description":"Distinct addresses targeted. A high count against a low attempt count is spraying; the inverse is a brute force against one account."},"failed_distinct_ips_24h":{"type":"integer","title":"Failed Distinct Ips 24H","description":"Distinct source addresses behind those failures."},"active_locks":{"type":"integer","title":"Active Locks","description":"Account locks currently in force."},"indefinite_locks":{"type":"integer","title":"Indefinite Locks","description":"Of those, locks that will not expire on their own."}},"type":"object","required":["failed_logins_24h","successful_logins_24h","failed_distinct_emails_24h","failed_distinct_ips_24h","active_locks","indefinite_locks"],"title":"AuthPosture"},"Body_anomaly_triage_admin_security_anomalies__signal_id___action__post":{"properties":{"note":{"type":"string","title":"Note","default":""},"csrf_token":{"type":"string","title":"Csrf Token","default":""}},"type":"object","title":"Body_anomaly_triage_admin_security_anomalies__signal_id___action__post"},"Body_clear_lock_admin_security_locks__lock_id__clear_post":{"properties":{"csrf_token":{"type":"string","title":"Csrf Token","default":""}},"type":"object","title":"Body_clear_lock_admin_security_locks__lock_id__clear_post"},"Body_do_unfreeze_admin_security_unfreeze_post":{"properties":{"note":{"type":"string","title":"Note","default":""},"csrf_token":{"type":"string","title":"Csrf Token","default":""}},"type":"object","title":"Body_do_unfreeze_admin_security_unfreeze_post"},"Body_freeze_admin_security_freeze_post":{"properties":{"reason":{"type":"string","title":"Reason"},"csrf_token":{"type":"string","title":"Csrf Token","default":""}},"type":"object","required":["reason"],"title":"Body_freeze_admin_security_freeze_post"},"Body_revert_containment_admin_security_containment__action_id__revert_post":{"properties":{"note":{"type":"string","title":"Note","default":""},"csrf_token":{"type":"string","title":"Csrf Token","default":""}},"type":"object","title":"Body_revert_containment_admin_security_containment__action_id__revert_post"},"BulkResult":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"affected":{"type":"integer","title":"Affected","description":"Number of rows changed."},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["affected"],"title":"BulkResult"},"ContainProviderRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why this action is being taken. Written into the permanent ContainmentAction record and the SecurityEvent. Minimum 8 characters."},"anomaly_signal_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Anomaly Signal Id","description":"Optional link to the AnomalySignal that prompted this, so detection and response are joined in the record (CSF RS.MA-02)."},"disable_credentials":{"type":"boolean","title":"Disable Credentials","description":"Also mark the provider's active vendor credentials disabled. Use when the upstream credential itself is suspected compromised rather than the traffic through it. Reverting will restore exactly the credentials this action disabled, and no others.","default":false}},"type":"object","required":["reason"],"title":"ContainProviderRequest"},"ContainUserRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why this action is being taken. Written into the permanent ContainmentAction record and the SecurityEvent. Minimum 8 characters."},"anomaly_signal_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Anomaly Signal Id","description":"Optional link to the AnomalySignal that prompted this, so detection and response are joined in the record (CSF RS.MA-02)."},"revoke_keys":{"type":"boolean","title":"Revoke Keys","description":"Also revoke every active NYU key belonging to this user. Default true: suspending an account while leaving its keys live contains nothing, because the proxy surface authenticates on the key.","default":true}},"type":"object","required":["reason"],"title":"ContainUserRequest"},"ContainmentEffect":{"properties":{"object_type":{"type":"string","title":"Object Type","description":"Table or entity affected, e.g. `api_key`."},"object_id":{"type":"string","title":"Object Id","description":"Id of the affected row."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Human label for the affected row."},"changed":{"additionalProperties":true,"type":"object","title":"Changed","description":"Field-by-field `{name: {'before': ..., 'after': ...}}` record of the change."}},"type":"object","required":["object_type","object_id"],"title":"ContainmentEffect","description":"What one containment action actually changed, recorded so a revert can\nrestore precisely the prior state and nothing else."},"ContainmentOut":{"properties":{"id":{"type":"string","title":"Id","description":"Action id."},"scope":{"type":"string","title":"Scope","description":"user, api_key, provider or global."},"action":{"type":"string","title":"Action","description":"suspend, revoke, disable or freeze_all_traffic."},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id","description":"Id of the contained object."},"target_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Label","description":"Readable label for that object."},"reason":{"type":"string","title":"Reason","description":"The written justification given when the action was applied."},"triggered_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who applied it."},"automated":{"type":"boolean","title":"Automated","description":"True when a detection rule acted with no human in the loop."},"anomaly_signal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anomaly Signal Id","description":"Signal that prompted the action, if it was linked."},"applied_at":{"type":"string","format":"date-time","title":"Applied At","description":"When containment was applied (UTC)."},"reverted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reverted At","description":"When it was lifted, if it was."},"reverted_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who lifted it."},"revert_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revert Note","description":"Written reason the action was lifted."},"active":{"type":"boolean","title":"Active","description":"True while the action has not been reverted."}},"type":"object","required":["id","scope","action","reason","automated","applied_at","active"],"title":"ContainmentOut","description":"A recorded containment action (CSF RS.MI-01, RS.MI-02). Survives revert:\n\"we froze traffic at 02:14 because X\" is the sentence an incident review\nneeds, and it must outlive the undo."},"ContainmentPosture":{"properties":{"active_total":{"type":"integer","title":"Active Total","description":"Containment actions currently in force."},"by_scope":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Scope","description":"Active actions per scope."},"traffic_frozen":{"type":"boolean","title":"Traffic Frozen","description":"True while the global freeze is engaged."},"freeze":{"$ref":"#/components/schemas/FreezeStatus","description":"Detail of the freeze, engaged or not."},"applied_7d":{"type":"integer","title":"Applied 7D","description":"Containment actions applied in the last 7 days."},"recent":{"items":{"$ref":"#/components/schemas/ContainmentOut"},"type":"array","title":"Recent","description":"Most recent actions, capped."}},"type":"object","required":["active_total","by_scope","traffic_frozen","freeze","applied_7d","recent"],"title":"ContainmentPosture"},"ContainmentRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why this action is being taken. Written into the permanent ContainmentAction record and the SecurityEvent. Minimum 8 characters."},"anomaly_signal_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Anomaly Signal Id","description":"Optional link to the AnomalySignal that prompted this, so detection and response are joined in the record (CSF RS.MA-02)."}},"type":"object","required":["reason"],"title":"ContainmentRequest","description":"Base body for every containment action.\n\n`reason` is required at the schema level, which means an omitted reason is\na 422 before a single row is touched - the refusal the brief asks for, and\nthe only way to guarantee the incident record is never empty."},"ContainmentResult":{"properties":{"action":{"$ref":"#/components/schemas/ContainmentOut","description":"The containment record, as stored."},"effects":{"items":{"$ref":"#/components/schemas/ContainmentEffect"},"type":"array","title":"Effects","description":"Exactly what changed. Empty means the target was already in the requested state - the action is still recorded, because the attempt is evidence."},"already_applied":{"type":"boolean","title":"Already Applied","description":"True when an equivalent containment was already active, so this call changed nothing. The operation is idempotent by design: a break-glass control that errors when pressed twice is a control nobody presses.","default":false},"message":{"type":"string","title":"Message","description":"What happened, in one sentence."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written."}},"type":"object","required":["action","effects","message"],"title":"ContainmentResult","description":"Response to applying or reverting containment."},"CredentialCreate":{"properties":{"name":{"type":"string","maxLength":120,"title":"Name","description":"Unique within the provider, e.g. `prod-2026-q3`."},"secret":{"type":"string","minLength":8,"title":"Secret","description":"The vendor API key. Encrypted at rest, never returned."},"weight":{"type":"integer","minimum":0.0,"title":"Weight","description":"0 parks the credential without disabling it.","default":1},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Per-credential overrides, e.g. a distinct Azure endpoint."},"status":{"type":"string","enum":["active","disabled"],"title":"Status","default":"active"}},"type":"object","required":["name","secret"],"title":"CredentialCreate"},"CredentialOut":{"properties":{"id":{"type":"string","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"},"name":{"type":"string","title":"Name"},"secret_last4":{"type":"string","title":"Secret Last4"},"status":{"type":"string","title":"Status"},"weight":{"type":"integer","title":"Weight","description":"Relative routing weight among active credentials."},"config":{"additionalProperties":true,"type":"object","title":"Config"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","provider_id","name","secret_last4","status","weight","created_at","updated_at"],"title":"CredentialOut","description":"Note the absence of any secret field. There is no endpoint, anywhere,\nthat returns an upstream vendor key after it is stored."},"CredentialRotate":{"properties":{"secret":{"type":"string","minLength":8,"title":"Secret","description":"The replacement vendor key."},"new_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"New Name","description":"Defaults to the old name with a date suffix."},"weight":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Weight","description":"Defaults to the old credential's weight."},"disable_old":{"type":"boolean","title":"Disable Old","description":"False leaves the old key active so you can drain traffic manually.","default":true}},"type":"object","required":["secret"],"title":"CredentialRotate","description":"Add-new-then-disable-old, as one call, so there is no window in which\nthe provider has no usable credential."},"CredentialRotateResult":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"new_credential":{"$ref":"#/components/schemas/CredentialOut"},"old_credential":{"$ref":"#/components/schemas/CredentialOut"}},"type":"object","required":["new_credential","old_credential"],"title":"CredentialRotateResult"},"CredentialUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Name"},"weight":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Weight"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"status":{"anyOf":[{"type":"string","enum":["active","disabled"]},{"type":"null"}],"title":"Status"}},"type":"object","title":"CredentialUpdate"},"DatasetPurgeResult":{"properties":{"dataset":{"type":"string","title":"Dataset","description":"Dataset acted on."},"retain_days":{"type":"integer","title":"Retain Days","description":"Window applied."},"cutoff":{"type":"string","format":"date-time","title":"Cutoff","description":"Rows older than this were in scope (UTC)."},"rows":{"type":"integer","title":"Rows","description":"Rows deleted, or that would be deleted on a dry run."},"skipped_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skipped Reason","description":"Why the dataset was skipped, or a note qualifying what was in scope - closed anomaly signals are aged out while open ones never are, for instance."}},"type":"object","required":["dataset","retain_days","cutoff","rows"],"title":"DatasetPurgeResult"},"DeprovisionResult":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"user_id":{"type":"string","title":"User Id"},"keys_revoked":{"type":"integer","title":"Keys Revoked","description":"API keys revoked as part of the same transaction."},"sessions_cleared":{"type":"integer","title":"Sessions Cleared","description":"Browser sessions destroyed."}},"type":"object","required":["user_id","keys_revoked","sessions_cleared"],"title":"DeprovisionResult"},"DetectionRunResult":{"properties":{"ran_at":{"type":"string","format":"date-time","title":"Ran At","description":"When the sweep started (UTC)."},"duration_ms":{"type":"integer","title":"Duration Ms","description":"How long the sweep took."},"detector_available":{"type":"boolean","title":"Detector Available","description":"False when no detection module is installed in this build. The endpoint still answers, so a missing detector is a visible condition rather than a 404."},"signals_touched":{"type":"integer","title":"Signals Touched","description":"Signals created or re-fired by this sweep (rows whose last_seen_at moved)."},"open_after":{"type":"integer","title":"Open After","description":"Total open signals after the sweep."},"signals":{"items":{"$ref":"#/components/schemas/AnomalyOut"},"type":"array","title":"Signals","description":"The signals this sweep created or updated, newest first."},"message":{"type":"string","title":"Message","description":"Human-readable outcome."}},"type":"object","required":["ran_at","duration_ms","detector_available","signals_touched","open_after","signals","message"],"title":"DetectionRunResult","description":"Outcome of an on-demand detection sweep."},"EventTypeBucket":{"properties":{"event_type":{"type":"string","title":"Event Type","description":"The event type this bucket counts."},"count":{"type":"integer","title":"Count","description":"Number of events of this type in the window."},"max_severity":{"type":"string","title":"Max Severity","description":"Highest severity seen for this type in the window."},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At","description":"Most recent occurrence in the window."}},"type":"object","required":["event_type","count","max_severity","last_seen_at"],"title":"EventTypeBucket","description":"One bar of the `/events/summary` chart."},"ExportedKey":{"properties":{"id":{"type":"string","title":"Id","description":"Key id."},"name":{"type":"string","title":"Name","description":"Label the key was given."},"key_prefix":{"type":"string","title":"Key Prefix","description":"Display-safe fragment. Not the key."},"status":{"type":"string","title":"Status","description":"active, revoked or expired."},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers","description":"Provider restriction, empty means all enabled."},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models","description":"Model restriction, empty means all enabled."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When it was issued."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Expiry, if set."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Last use."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"Revocation time, if revoked."}},"type":"object","required":["id","name","key_prefix","status","allowed_providers","allowed_models","created_at"],"title":"ExportedKey","description":"A key as it appears in a subject data export. The hash is not included -\nit is a verifier for a bearer credential, and an export is a document that\nleaves the building."},"ExportedUsageRow":{"properties":{"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug","description":"Vendor the tokens went to."},"public_alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Alias","description":"Model alias used."},"requests":{"type":"integer","title":"Requests","description":"Requests made."},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","description":"Prompt tokens consumed."},"completion_tokens":{"type":"integer","title":"Completion Tokens","description":"Completion tokens consumed."},"total_tokens":{"type":"integer","title":"Total Tokens","description":"Total tokens consumed."},"first_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Seen At","description":"Earliest request in the window."},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"Latest request in the window."}},"type":"object","required":["requests","prompt_tokens","completion_tokens","total_tokens"],"title":"ExportedUsageRow","description":"Aggregated consumption. The export deliberately carries totals rather\nthan per-request rows: the subject's own prompts are not stored, and a\nrequest-by-request list is surveillance, not access."},"FreezeRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why this action is being taken. Written into the permanent ContainmentAction record and the SecurityEvent. Minimum 8 characters."},"anomaly_signal_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Anomaly Signal Id","description":"Optional link to the AnomalySignal that prompted this, so detection and response are joined in the record (CSF RS.MA-02)."}},"type":"object","required":["reason"],"title":"FreezeRequest","description":"Global stop. Distinct model so the OpenAPI page shows it as its own,\nclearly labelled, break-glass operation."},"FreezeStatus":{"properties":{"frozen":{"type":"boolean","title":"Frozen","description":"True while a global freeze is in force."},"since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Since","description":"When the active freeze began."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why it was frozen."},"frozen_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who froze it."},"action_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Id","description":"Id of the active freeze action."}},"type":"object","required":["frozen"],"title":"FreezeStatus","description":"Whether the gateway is currently refusing all proxy traffic."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"KeyInfo":{"properties":{"object":{"type":"string","const":"api_key","title":"Object","default":"api_key"},"key_prefix":{"type":"string","title":"Key Prefix","description":"The key's first characters, enough to tell two keys apart."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The label its owner gave it."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Null when the key does not expire on its own."},"scope":{"$ref":"#/components/schemas/KeyScope"},"quota_enforcement":{"type":"string","title":"Quota Enforcement","description":"`hard` refuses a call at the limit; `soft` allows overage and flags it."},"providers":{"items":{"$ref":"#/components/schemas/KeyProvider"},"type":"array","title":"Providers","description":"Exactly what this key can reach right now."}},"type":"object","required":["key_prefix","scope","quota_enforcement","providers"],"title":"KeyInfo","description":"Everything a key entitles its holder to know - and nothing about its owner."},"KeyPosture":{"properties":{"active_keys":{"type":"integer","title":"Active Keys","description":"NYU keys currently able to spend."},"issued_7d":{"type":"integer","title":"Issued 7D","description":"Keys issued in the last 7 days."},"revoked_7d":{"type":"integer","title":"Revoked 7D","description":"Keys revoked in the last 7 days."},"expiring_30d":{"type":"integer","title":"Expiring 30D","description":"Active keys expiring within 30 days."},"never_used":{"type":"integer","title":"Never Used","description":"Active keys never used. Standing credentials nobody needs are the cheapest thing to take away (CSF PR.AA-05)."}},"type":"object","required":["active_keys","issued_7d","revoked_7d","expiring_30d","never_used"],"title":"KeyPosture"},"KeyProvider":{"properties":{"id":{"type":"string","title":"Id","description":"Provider slug. The same value `/v1/models` reports as `owned_by`."},"object":{"type":"string","const":"provider","title":"Object","default":"provider"},"display_name":{"type":"string","title":"Display Name","description":"The vendor's name, for a picker or a badge."},"models":{"items":{"type":"string"},"type":"array","title":"Models","description":"Public aliases this key may send as `model`, in catalogue order."},"mock":{"type":"boolean","title":"Mock","description":"True when NYU holds no active credential for this vendor, so calls are answered by the mock adapter with real token accounting. Responses also carry `X-NYU-Mock: true`."},"quota":{"anyOf":[{"$ref":"#/components/schemas/KeyQuota"},{"type":"null"}],"description":"Absent only if the quota ledger has no row for this provider."}},"type":"object","required":["id","display_name","models","mock"],"title":"KeyProvider","description":"A third-party vendor this key is cleared to reach, and its standing."},"KeyProviderList":{"properties":{"object":{"type":"string","const":"list","title":"Object","default":"list"},"data":{"items":{"$ref":"#/components/schemas/KeyProvider"},"type":"array","title":"Data"}},"type":"object","required":["data"],"title":"KeyProviderList"},"KeyQuota":{"properties":{"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit","description":"Null = explicitly unlimited."},"used":{"type":"integer","title":"Used","default":0},"remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Remaining","description":"Null when unlimited. Floored at 0."},"window":{"type":"string","title":"Window","description":"`monthly`, `weekly` or `daily`."},"window_key":{"type":"string","title":"Window Key","description":"The current period label, e.g. \"2026-09\". A reset is a new key."},"resets_at":{"type":"string","format":"date-time","title":"Resets At","description":"UTC instant the window rolls over."},"exhausted":{"type":"boolean","title":"Exhausted","default":false}},"type":"object","required":["window","window_key","resets_at"],"title":"KeyQuota","description":"One provider's token standing for the key that asked.\n\nRead from `quota_counters`, the durable ledger - so a call still in flight\nis not counted here yet. The `X-NYU-Tokens-*` headers on a real request are\nthe live view, including reservations; this is the settled one."},"KeyRequestApprove":{"properties":{"key_name":{"type":"string","maxLength":120,"title":"Key Name","default":"default"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days"},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers"},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models"},"allowed_credentials":{"items":{"type":"string"},"type":"array","title":"Allowed Credentials","description":"Pin the issued key to these vendor credential ids."},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id","description":"Also assign this policy to the user as part of approval."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"KeyRequestApprove"},"KeyRequestApproved":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"request":{"$ref":"#/components/schemas/KeyRequestOut"},"api_key":{"$ref":"#/components/schemas/ApiKeyCreated","description":"Contains the plaintext key. Relay it once, then discard."}},"type":"object","required":["request","api_key"],"title":"KeyRequestApproved"},"KeyRequestCreate":{"properties":{"justification":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Justification","description":"What the key is for. The internal team reads this."},"department":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Department","description":"Your NYU school or office, from the list at GET /api/portal/nyu-units. Omitted, the gateway uses the one already on your account."},"requested_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Policy Id","description":"Tier the user believes they need."}},"type":"object","title":"KeyRequestCreate"},"KeyRequestDeny":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Shown to the requester in the portal."}},"type":"object","title":"KeyRequestDeny"},"KeyRequestOut":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"user_full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Full Name"},"justification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Justification"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"requested_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Policy Id"},"requested_policy_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Policy Name"},"status":{"type":"string","title":"Status"},"decided_by_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By Id"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decided At"},"decision_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision Note"},"issued_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issued Key Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","status","created_at","updated_at"],"title":"KeyRequestOut"},"KeyScope":{"properties":{"all_providers":{"type":"boolean","title":"All Providers","description":"True when the key is not restricted to named providers."},"allowed_providers":{"items":{"type":"string"},"type":"array","title":"Allowed Providers","description":"Provider slugs, when it is. Empty otherwise."},"all_models":{"type":"boolean","title":"All Models","description":"True when the key is not restricted to named models."},"allowed_models":{"items":{"type":"string"},"type":"array","title":"Allowed Models","description":"Public aliases, when it is. Empty otherwise."}},"type":"object","required":["all_providers","allowed_providers","all_models","allowed_models"],"title":"KeyScope","description":"What the key's allow-lists say, spelled out.\n\nAn empty allow-list in the database means *everything NYU currently\nenables*, which reads as the opposite of what it is. The booleans say it\nout loud so an app never has to infer permission from an empty array."},"LockClearResult":{"properties":{"lock":{"$ref":"#/components/schemas/AccountLockOut","description":"The lock, as it now stands."},"cache_cleared":{"type":"boolean","title":"Cache Cleared","description":"True when the hot lockout counter was also cleared. False means the durable lock is lifted but a cached counter may still reject sign-in until its window expires - stated plainly rather than assumed."},"message":{"type":"string","title":"Message","description":"What happened, in one sentence."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written."}},"type":"object","required":["lock","cache_cleared","message"],"title":"LockClearResult"},"MessageResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"message":{"type":"string","title":"Message","description":"What happened, in a sentence."}},"type":"object","required":["message"],"title":"MessageResponse"},"MfaConfirmRequest":{"properties":{"code":{"type":"string","maxLength":32,"minLength":6,"title":"Code","description":"Current 6-digit code from the authenticator app."}},"type":"object","required":["code"],"title":"MfaConfirmRequest","description":"Prove possession before the factor counts. An unconfirmed enrolment is\nnever treated as coverage, otherwise an operator could appear compliant\nwhile holding a seed they never loaded into an authenticator."},"MfaConfirmResult":{"properties":{"confirmed_at":{"type":"string","format":"date-time","title":"Confirmed At","description":"When the factor became active (UTC)."},"message":{"type":"string","title":"Message","description":"What happened, in one sentence."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written."}},"type":"object","required":["confirmed_at","message"],"title":"MfaConfirmResult"},"MfaCoverageRow":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Operator id."},"email":{"type":"string","title":"Email","description":"Operator email."},"role":{"type":"string","title":"Role","description":"admin or owner."},"enrolled":{"type":"boolean","title":"Enrolled","description":"A credential row exists."},"confirmed":{"type":"boolean","title":"Confirmed","description":"Possession was proven."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Last successful second-factor use."}},"type":"object","required":["user_id","email","role","enrolled","confirmed"],"title":"MfaCoverageRow","description":"One privileged account's MFA state, for the posture panel."},"MfaDisableRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":4,"title":"Reason","description":"Why the factor is being removed."},"code":{"anyOf":[{"type":"string","maxLength":32,"minLength":6},{"type":"null"}],"title":"Code","description":"Current TOTP code or an unused recovery code. Required when disabling your own factor; ignored on an owner reset of another account."},"user_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"User Id","description":"Reset another operator's factor. Owner role only. Omit for self-service."}},"type":"object","required":["reason"],"title":"MfaDisableRequest","description":"Removing a second factor.\n\nTwo paths, and the difference is the control:\n\n* **Self-service** - the operator must supply a *current* code or an unused\n  recovery code. Without that, anyone holding a stolen session cookie could\n  quietly strip the factor that would have stopped them.\n* **Owner reset** - the gateway owner may clear another operator's factor\n  without a code, for the lost-phone case. That path is restricted to the\n  `owner` role and always writes a high-severity event, so the separation\n  of duty is enforced and visible (CSF PR.AA-05, GV.RR-02)."},"MfaEnrollRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label","description":"Optional label shown in the authenticator app, e.g. the device name."}},"type":"object","title":"MfaEnrollRequest","description":"Start TOTP enrolment for the calling operator.\n\nThere is no `user_id`: enrolment is always self-service. An administrator\nwho could enrol a second factor *for* another account would by definition\nhold that account's factor, which converts MFA from an access control into\nan impersonation tool (threat: insider misuse)."},"MfaEnrollResult":{"properties":{"provisioning_uri":{"type":"string","title":"Provisioning Uri","description":"`otpauth://totp/...` URI to render as a QR code. Shown once. Treat as a secret: it carries the shared seed."},"recovery_codes":{"items":{"type":"string"},"type":"array","title":"Recovery Codes","description":"Single-use recovery codes, shown once. Stored only as peppered HMAC hashes, so they cannot be recovered from a database dump."},"issuer":{"type":"string","title":"Issuer","description":"Issuer label embedded in the URI."},"account_name":{"type":"string","title":"Account Name","description":"Account label embedded in the URI."},"digits":{"type":"integer","title":"Digits","description":"TOTP digit count (RFC 6238)."},"period_seconds":{"type":"integer","title":"Period Seconds","description":"TOTP time step in seconds (RFC 6238)."},"algorithm":{"type":"string","title":"Algorithm","description":"TOTP HMAC algorithm (RFC 6238)."},"message":{"type":"string","title":"Message","description":"Next step: the enrolment is not active until confirmed with a live code."}},"type":"object","required":["provisioning_uri","recovery_codes","issuer","account_name","digits","period_seconds","algorithm","message"],"title":"MfaEnrollResult","description":"Returned exactly once, at enrolment, to the enrolling operator.\n\nThe `otpauth://` URI necessarily contains the shared seed - that is what\nenrolment *is*. It is therefore never stored in a log, never written into a\nSecurityEvent, and cannot be retrieved again: a lost enrolment is restarted,\nnot recovered. `GET /mfa/status` will never return it."},"MfaPosture":{"properties":{"required_for_admins":{"type":"boolean","title":"Required For Admins","description":"Whether this deployment enforces a second factor."},"admin_accounts":{"type":"integer","title":"Admin Accounts","description":"Active admin and owner accounts."},"enrolled":{"type":"integer","title":"Enrolled","description":"Of those, accounts with a confirmed factor."},"unenrolled":{"type":"integer","title":"Unenrolled","description":"Of those, accounts with no confirmed factor."},"coverage_pct":{"type":"number","title":"Coverage Pct","description":"Enrolled as a percentage of privileged accounts."},"rows":{"items":{"$ref":"#/components/schemas/MfaCoverageRow"},"type":"array","title":"Rows","description":"Per-account state, capped. Contains no secrets of any kind."}},"type":"object","required":["required_for_admins","admin_accounts","enrolled","unenrolled","coverage_pct","rows"],"title":"MfaPosture","description":"MFA coverage across privileged accounts (CSF PR.AA-03, PR.AA-05)."},"MfaStatusOut":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Whose enrolment this describes."},"email":{"type":"string","title":"Email","description":"That operator's email."},"enrolled":{"type":"boolean","title":"Enrolled","description":"True when a credential row exists."},"confirmed":{"type":"boolean","title":"Confirmed","description":"True when possession has been proven. Only confirmed factors count as coverage."},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At","description":"When it was confirmed."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Last successful use."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Device label given at enrolment."},"recovery_codes_remaining":{"type":"integer","title":"Recovery Codes Remaining","description":"Unused recovery codes left."},"required":{"type":"boolean","title":"Required","description":"True when this deployment requires a second factor for privileged roles."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When enrolment began."}},"type":"object","required":["user_id","email","enrolled","confirmed","recovery_codes_remaining","required"],"title":"MfaStatusOut","description":"Enrolment state. Carries no seed, no URI and no recovery codes - only\ncounts and timestamps."},"ModelCreate":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"public_alias":{"type":"string","maxLength":160,"title":"Public Alias"},"upstream_model":{"type":"string","maxLength":200,"title":"Upstream Model"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"modality":{"type":"string","enum":["chat","embedding"],"title":"Modality","default":"chat"},"context_window":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Context Window"},"max_output_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Output Tokens"},"input_cost_per_mtok":{"type":"number","minimum":0.0,"title":"Input Cost Per Mtok","default":0},"output_cost_per_mtok":{"type":"number","minimum":0.0,"title":"Output Cost Per Mtok","default":0},"cached_input_cost_per_mtok":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Cached Input Cost Per Mtok"},"enabled":{"type":"boolean","title":"Enabled","default":true},"deprecated":{"type":"boolean","title":"Deprecated","default":false}},"type":"object","required":["provider_id","public_alias","upstream_model"],"title":"ModelCreate"},"ModelOut":{"properties":{"id":{"type":"string","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"provider_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Display Name"},"public_alias":{"type":"string","title":"Public Alias","description":"What callers put in the request body."},"upstream_model":{"type":"string","title":"Upstream Model","description":"What we send the vendor (the Azure deployment name, etc)."},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"modality":{"type":"string","title":"Modality"},"context_window":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Context Window"},"max_output_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Output Tokens"},"input_cost_per_mtok":{"type":"number","title":"Input Cost Per Mtok","description":"USD per million input tokens."},"output_cost_per_mtok":{"type":"number","title":"Output Cost Per Mtok","description":"USD per million output tokens."},"cached_input_cost_per_mtok":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cached Input Cost Per Mtok"},"enabled":{"type":"boolean","title":"Enabled"},"deprecated":{"type":"boolean","title":"Deprecated"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","provider_id","public_alias","upstream_model","modality","input_cost_per_mtok","output_cost_per_mtok","enabled","deprecated","created_at","updated_at"],"title":"ModelOut"},"ModelUpdate":{"properties":{"public_alias":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Public Alias"},"upstream_model":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Upstream Model"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"modality":{"anyOf":[{"type":"string","enum":["chat","embedding"]},{"type":"null"}],"title":"Modality"},"context_window":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Context Window"},"max_output_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Output Tokens"},"input_cost_per_mtok":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Input Cost Per Mtok"},"output_cost_per_mtok":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Output Cost Per Mtok"},"cached_input_cost_per_mtok":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Cached Input Cost Per Mtok"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"deprecated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Deprecated"}},"type":"object","title":"ModelUpdate"},"ModelUsageRow":{"properties":{"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id"},"public_alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Alias"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0},"requests":{"type":"integer","title":"Requests","default":0},"errors":{"type":"integer","title":"Errors","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0}},"type":"object","title":"ModelUsageRow"},"NyuUnitGroup":{"properties":{"group":{"type":"string","title":"Group"},"units":{"items":{"type":"string"},"type":"array","title":"Units"}},"type":"object","required":["group","units"],"title":"NyuUnitGroup","description":"One heading from the school/office list, with what sits under it."},"OverrideCreate":{"properties":{"user_id":{"type":"string","title":"User Id"},"provider_id":{"type":"string","title":"Provider Id"},"token_limit":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Token Limit","description":"`null` = unlimited for this user."},"window":{"anyOf":[{"type":"string","enum":["daily","weekly","monthly"]},{"type":"null"}],"title":"Window"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Recorded in the audit log. Write one."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"After this instant the override stops applying and the policy resumes."}},"type":"object","required":["user_id","provider_id"],"title":"OverrideCreate"},"OverrideOut":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"provider_id":{"type":"string","title":"Provider Id"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"token_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Token Limit"},"window":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Window","description":"Null inherits the user's policy window."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","provider_id","created_at","updated_at"],"title":"OverrideOut"},"OverrideUpdate":{"properties":{"token_limit":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Token Limit"},"window":{"anyOf":[{"type":"string","enum":["daily","weekly","monthly"]},{"type":"null"}],"title":"Window"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","title":"OverrideUpdate","description":"Send `null` explicitly to clear a field; omit it to leave it alone.\n`token_limit: null` therefore means \"unlimited\", not \"unset\"."},"Page_AccountLockOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AccountLockOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[AccountLockOut]"},"Page_AnomalyOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AnomalyOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[AnomalyOut]"},"Page_ApiKeyOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ApiKeyOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[ApiKeyOut]"},"Page_AuditLogOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AuditLogOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[AuditLogOut]"},"Page_ContainmentOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ContainmentOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[ContainmentOut]"},"Page_KeyRequestOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/KeyRequestOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[KeyRequestOut]"},"Page_ModelOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ModelOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[ModelOut]"},"Page_OverrideOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OverrideOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[OverrideOut]"},"Page_SecurityEventOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SecurityEventOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[SecurityEventOut]"},"Page_UsageEventOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UsageEventOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[UsageEventOut]"},"Page_UserOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UserOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[UserOut]"},"Page_VendorAssessmentOut_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VendorAssessmentOut"},"type":"array","title":"Items","description":"The page of results."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filter, ignoring limit/offset."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Row offset that was applied."}},"type":"object","required":["items","total","limit","offset"],"title":"Page[VendorAssessmentOut]"},"PolicyAssignRequest":{"properties":{"user_ids":{"items":{"type":"string"},"type":"array","maxItems":5000,"minItems":1,"title":"User Ids","description":"Users to move onto this policy."}},"type":"object","required":["user_ids"],"title":"PolicyAssignRequest"},"PortalCapabilities":{"properties":{"self_service_keys":{"type":"boolean","title":"Self Service Keys","description":"May the user mint their own key right now?"},"key_requests_enabled":{"type":"boolean","title":"Key Requests Enabled","description":"Should the 'request a key' form be shown instead?"},"sso_enabled":{"type":"boolean","title":"Sso Enabled"}},"type":"object","required":["self_service_keys","key_requests_enabled","sso_enabled"],"title":"PortalCapabilities","description":"What the portal UI should let this user do. Computed server-side so the\nfront end never has to reason about the feature flags."},"PortalMe":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"netid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Netid"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"affiliation":{"type":"string","title":"Affiliation"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status"},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id"},"quota_policy_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Name"},"quota_window":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Window"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"base_url":{"type":"string","title":"Base Url","description":"Point your OpenAI-compatible SDK here."},"capabilities":{"$ref":"#/components/schemas/PortalCapabilities"},"active_key_count":{"type":"integer","title":"Active Key Count","default":0},"pending_key_requests":{"type":"integer","title":"Pending Key Requests","default":0}},"type":"object","required":["id","email","affiliation","role","status","created_at","base_url","capabilities"],"title":"PortalMe"},"PortalModel":{"properties":{"public_alias":{"type":"string","title":"Public Alias"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"modality":{"type":"string","title":"Modality"},"context_window":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Context Window"},"max_output_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Output Tokens"},"input_cost_per_mtok":{"type":"number","title":"Input Cost Per Mtok"},"output_cost_per_mtok":{"type":"number","title":"Output Cost Per Mtok"},"deprecated":{"type":"boolean","title":"Deprecated"}},"type":"object","required":["public_alias","modality","input_cost_per_mtok","output_cost_per_mtok","deprecated"],"title":"PortalModel"},"PortalProviderModels":{"properties":{"provider_slug":{"type":"string","title":"Provider Slug"},"provider_display_name":{"type":"string","title":"Provider Display Name"},"models":{"items":{"$ref":"#/components/schemas/PortalModel"},"type":"array","title":"Models"}},"type":"object","required":["provider_slug","provider_display_name","models"],"title":"PortalProviderModels"},"PortalQuota":{"properties":{"user_id":{"type":"string","title":"User Id"},"enforcement":{"type":"string","title":"Enforcement"},"providers":{"items":{"$ref":"#/components/schemas/ProviderQuotaStatus"},"type":"array","title":"Providers"}},"type":"object","required":["user_id","enforcement","providers"],"title":"PortalQuota"},"PortalUsage":{"properties":{"start":{"type":"string","format":"date","title":"Start"},"end":{"type":"string","format":"date","title":"End"},"totals":{"$ref":"#/components/schemas/UsageTotals"},"by_provider":{"items":{"$ref":"#/components/schemas/ProviderUsageRow"},"type":"array","title":"By Provider"},"by_model":{"items":{"$ref":"#/components/schemas/ModelUsageRow"},"type":"array","title":"By Model"},"timeseries":{"items":{"$ref":"#/components/schemas/TimeseriesPoint"},"type":"array","title":"Timeseries"},"recent":{"items":{"$ref":"#/components/schemas/UsageEventOut"},"type":"array","title":"Recent","description":"Most recent requests, newest first."}},"type":"object","required":["start","end","totals","by_provider","by_model","timeseries","recent"],"title":"PortalUsage"},"ProviderCreate":{"properties":{"slug":{"type":"string","maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Slug","description":"Stable identifier used in key allow-lists and usage reports."},"display_name":{"type":"string","maxLength":120,"title":"Display Name"},"adapter":{"type":"string","maxLength":64,"title":"Adapter","description":"Adapter key. Use `openai_compat` for any OpenAI-shaped vendor.","default":"openai_compat"},"base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Url","description":"Vendor API root. Null uses the adapter default."},"enabled":{"type":"boolean","title":"Enabled","default":true},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Adapter knobs, e.g. {\"api_version\": \"2024-10-21\"} for Azure."},"sort_order":{"type":"integer","title":"Sort Order","default":100}},"type":"object","required":["slug","display_name"],"title":"ProviderCreate"},"ProviderEnabledUpdate":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"ProviderEnabledUpdate"},"ProviderOut":{"properties":{"id":{"type":"string","title":"Id"},"slug":{"type":"string","title":"Slug"},"display_name":{"type":"string","title":"Display Name"},"adapter":{"type":"string","title":"Adapter","description":"Wire protocol. `openai_compat` covers most vendors."},"base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Url"},"enabled":{"type":"boolean","title":"Enabled"},"config":{"additionalProperties":true,"type":"object","title":"Config"},"sort_order":{"type":"integer","title":"Sort Order"},"credential_count":{"type":"integer","title":"Credential Count","default":0},"active_credential_count":{"type":"integer","title":"Active Credential Count","default":0},"model_count":{"type":"integer","title":"Model Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","slug","display_name","adapter","enabled","sort_order","created_at","updated_at"],"title":"ProviderOut"},"ProviderPosture":{"properties":{"total":{"type":"integer","title":"Total","description":"Providers configured."},"enabled":{"type":"integer","title":"Enabled","description":"Providers routable."},"without_credential":{"items":{"$ref":"#/components/schemas/ProviderSafetySignal"},"type":"array","title":"Without Credential","description":"Enabled providers holding no active vendor credential - running in mock mode."},"mock_mode_count":{"type":"integer","title":"Mock Mode Count","description":"How many enabled providers are in that state."},"fully_live":{"type":"boolean","title":"Fully Live","description":"True when every enabled provider holds at least one active credential."}},"type":"object","required":["total","enabled","without_credential","mock_mode_count","fully_live"],"title":"ProviderPosture"},"ProviderQuotaStatus":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"provider_slug":{"type":"string","title":"Provider Slug"},"provider_display_name":{"type":"string","title":"Provider Display Name"},"enabled":{"type":"boolean","title":"Enabled"},"window":{"type":"string","title":"Window"},"window_key":{"type":"string","title":"Window Key","description":"Canonical period label, e.g. \"2026-09\". A reset is a new key."},"token_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Token Limit","description":"Null = unlimited."},"tokens_used":{"type":"integer","title":"Tokens Used","default":0},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Remaining","description":"Null when unlimited. Floored at 0."},"percent_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Percent Used","description":"Null when unlimited. 0-100, and may exceed 100 under soft enforcement."},"requests":{"type":"integer","title":"Requests","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0},"resets_at":{"type":"string","format":"date-time","title":"Resets At","description":"UTC instant the window rolls over."},"source":{"type":"string","enum":["override","policy","default_policy","unlimited"],"title":"Source","description":"Which level of quota resolution supplied the limit."},"exhausted":{"type":"boolean","title":"Exhausted","default":false}},"type":"object","required":["provider_id","provider_slug","provider_display_name","enabled","window","window_key","resets_at","source"],"title":"ProviderQuotaStatus"},"ProviderSafetySignal":{"properties":{"provider_id":{"type":"string","title":"Provider Id","description":"Provider id."},"slug":{"type":"string","title":"Slug","description":"Provider slug."},"display_name":{"type":"string","title":"Display Name","description":"Provider name."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether routing to this provider is switched on."},"active_credentials":{"type":"integer","title":"Active Credentials","description":"Usable vendor credentials held. Zero means mock mode."}},"type":"object","required":["provider_id","slug","display_name","enabled","active_credentials"],"title":"ProviderSafetySignal","description":"A provider with no usable vendor credential.\n\nReported as a *safety* signal rather than an error: no credential means no\nreal spend is possible through this vendor, which is a containment-adjacent\ngood, but it also means no real service. Both facts matter, so the console\nshows the condition rather than hiding it as a configuration warning."},"ProviderTestRequest":{"properties":{"credential_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Id","description":"Which credential to exercise. Defaults to the highest-weight active one."},"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id","description":"Which model to call. Defaults to the cheapest enabled chat model."},"prompt":{"type":"string","maxLength":500,"title":"Prompt","description":"Kept tiny on purpose - this costs money.","default":"ping"}},"type":"object","title":"ProviderTestRequest"},"ProviderTestResult":{"properties":{"ok":{"type":"boolean","title":"Ok"},"provider_id":{"type":"string","title":"Provider Id"},"provider_slug":{"type":"string","title":"Provider Slug"},"credential_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Id"},"credential_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Name"},"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id"},"public_alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Alias"},"upstream_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upstream Model"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"},"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status"},"error_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Type"},"message":{"type":"string","title":"Message","description":"Success summary, or the upstream failure reason."},"sample_output":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sample Output","description":"First 200 chars of the completion."},"usage":{"anyOf":[{"$ref":"#/components/schemas/ProviderTestUsage"},{"type":"null"}]}},"type":"object","required":["ok","provider_id","provider_slug","message"],"title":"ProviderTestResult","description":"The answer to \"does this key actually work\" - asked before students hit it."},"ProviderTestUsage":{"properties":{"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0}},"type":"object","title":"ProviderTestUsage"},"ProviderUpdate":{"properties":{"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"adapter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Adapter"},"base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Url"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"sort_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sort Order"}},"type":"object","title":"ProviderUpdate"},"ProviderUsageRow":{"properties":{"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"provider_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Display Name"},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0},"requests":{"type":"integer","title":"Requests","default":0},"errors":{"type":"integer","title":"Errors","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0}},"type":"object","title":"ProviderUsageRow"},"QuotaLimitIn":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"token_limit":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Token Limit","description":"Tokens per window. `null` means explicitly UNLIMITED for this provider - which is not the same as omitting the provider, which falls through to the default policy."}},"type":"object","required":["provider_id"],"title":"QuotaLimitIn"},"QuotaLimitOut":{"properties":{"id":{"type":"string","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"provider_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Display Name"},"token_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Token Limit"}},"type":"object","required":["id","provider_id"],"title":"QuotaLimitOut"},"QuotaPolicyCreate":{"properties":{"name":{"type":"string","maxLength":120,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"window":{"type":"string","enum":["daily","weekly","monthly"],"title":"Window","default":"monthly"},"is_default":{"type":"boolean","title":"Is Default","description":"Setting true demotes whichever policy is currently default.","default":false},"auto_assign_affiliation":{"anyOf":[{"type":"string","enum":["student","faculty","staff","researcher","affiliate","other"]},{"type":"null"}],"title":"Auto Assign Affiliation"},"limits":{"items":{"$ref":"#/components/schemas/QuotaLimitIn"},"type":"array","title":"Limits"}},"type":"object","required":["name"],"title":"QuotaPolicyCreate"},"QuotaPolicyOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"window":{"type":"string","title":"Window"},"is_default":{"type":"boolean","title":"Is Default"},"auto_assign_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auto Assign Affiliation","description":"An SSO claim with this affiliation lands new users on this tier."},"limits":{"items":{"$ref":"#/components/schemas/QuotaLimitOut"},"type":"array","title":"Limits"},"user_count":{"type":"integer","title":"User Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","window","is_default","created_at","updated_at"],"title":"QuotaPolicyOut"},"QuotaPolicyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"window":{"anyOf":[{"type":"string","enum":["daily","weekly","monthly"]},{"type":"null"}],"title":"Window"},"is_default":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Default"},"auto_assign_affiliation":{"anyOf":[{"type":"string","enum":["student","faculty","staff","researcher","affiliate","other"]},{"type":"null"}],"title":"Auto Assign Affiliation"},"limits":{"anyOf":[{"items":{"$ref":"#/components/schemas/QuotaLimitIn"},"type":"array"},{"type":"null"}],"title":"Limits","description":"When sent, REPLACES the whole limit set. Omit to leave limits alone."}},"type":"object","title":"QuotaPolicyUpdate"},"RetentionPolicyOut":{"properties":{"id":{"type":"string","title":"Id","description":"Policy id."},"dataset":{"type":"string","title":"Dataset","description":"Dataset the window applies to, e.g. `usage_events`."},"retain_days":{"type":"integer","title":"Retain Days","description":"How many days of this dataset are kept."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Why this window was chosen."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the purge acts on this dataset."},"last_purged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Purged At","description":"Last successful purge run."},"last_purged_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Purged Rows","description":"Rows removed by that run."},"purgeable":{"type":"boolean","title":"Purgeable","description":"False when the console has no executor for this dataset name, so a policy that would silently never run is visible rather than assumed effective."},"rows_over_retention":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Over Retention","description":"Rows currently older than the window. Only computed when the request asks for an estimate, because it is a full count over the largest tables."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation time."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last modification time."}},"type":"object","required":["id","dataset","retain_days","enabled","purgeable","created_at","updated_at"],"title":"RetentionPolicyOut","description":"A declarative retention window (CSF PR.DS-10, ID.AM-08)."},"RetentionPolicyUpdate":{"properties":{"retain_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Retain Days","description":"New retention window, in days."},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled","description":"Enable or disable purging for this dataset."},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description","description":"Updated justification."},"reason":{"type":"string","maxLength":2000,"minLength":4,"title":"Reason","description":"Why this retention window is being changed."}},"type":"object","required":["reason"],"title":"RetentionPolicyUpdate","description":"Changing a retention window.\n\n`reason` is required, and shortening a window is treated as a high-severity\nevent. Retention is the one setting an insider can use to destroy the\nevidence of their own actions, so the change itself has to leave a trace\nthat the change cannot remove (CSF PR.DS-10, GV.RR-02, DE.AE-08)."},"RetentionPosture":{"properties":{"policies":{"type":"integer","title":"Policies","description":"Retention policies configured."},"disabled_policies":{"type":"integer","title":"Disabled Policies","description":"Policies that exist but are switched off."},"never_purged":{"type":"integer","title":"Never Purged","description":"Enabled policies that have never run."},"oldest_last_purged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Oldest Last Purged At","description":"Least recently purged dataset's last run."},"prompt_content_stored":{"type":"boolean","title":"Prompt Content Stored","description":"Whether the gateway is currently persisting prompt and response content. Should be false in production: storing it turns the gateway into an archive of potentially FERPA-relevant student work (CSF PR.DS-01)."}},"type":"object","required":["policies","disabled_policies","never_purged","prompt_content_stored"],"title":"RetentionPosture"},"RetentionPurgeRequest":{"properties":{"datasets":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":32},{"type":"null"}],"title":"Datasets","description":"Restrict the run to these datasets. Omit for every enabled policy."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Count what would be deleted without deleting it. The safe first click.","default":false},"reason":{"type":"string","maxLength":2000,"minLength":4,"title":"Reason","description":"Why the purge is being run by hand."}},"type":"object","required":["reason"],"title":"RetentionPurgeRequest","description":"Run the purge now rather than waiting for the schedule."},"RetentionPurgeResult":{"properties":{"ran_at":{"type":"string","format":"date-time","title":"Ran At","description":"When the run started (UTC)."},"dry_run":{"type":"boolean","title":"Dry Run","description":"True when nothing was actually deleted."},"total_rows":{"type":"integer","title":"Total Rows","description":"Rows across all datasets."},"results":{"items":{"$ref":"#/components/schemas/DatasetPurgeResult"},"type":"array","title":"Results","description":"Per-dataset outcome."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written."}},"type":"object","required":["ran_at","dry_run","total_rows","results"],"title":"RetentionPurgeResult"},"RevertRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why the containment is being lifted. Recorded as the revert note on the original action, which is never deleted. Minimum 8 characters."}},"type":"object","required":["reason"],"title":"RevertRequest","description":"Undoing a containment action. Also requires a written reason: restoring\naccess during a live incident is at least as consequential as cutting it."},"SecurityEventOut":{"properties":{"id":{"type":"string","title":"Id","description":"Event id."},"event_type":{"type":"string","title":"Event Type","description":"Event type from the closed vocabulary in `security_models.SECURITY_EVENT_TYPES`."},"severity":{"type":"string","title":"Severity","description":"One of info, low, medium, high, critical."},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Subject user, when the event has one. Null for unattributed events - a failed login or an unknown key, which is exactly what an attacker generates."},"actor_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Email","description":"Email associated with the event, as recorded at the time."},"api_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id","description":"NYU-issued key involved, if any. Never the key itself."},"target_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Type","description":"Kind of object the event concerns, when it is not the actor."},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id","description":"Id of that object."},"client_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Ip","description":"Caller address, as resolved by the proxy policy."},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent","description":"Caller user agent, truncated."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Correlates with the usage row and the access log for the same request."},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Short human-readable summary."},"detail":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Detail","description":"Structured context, redacted at the API boundary: credential-shaped values and any prompt or response content are removed before the payload leaves the gateway."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the event was recorded (UTC)."}},"type":"object","required":["id","event_type","severity","created_at"],"title":"SecurityEventOut","description":"One row of security telemetry (CSF DE.CM-09, DE.AE-03).\n\n`detail` is redacted on the way out; see :func:`redact_payload`."},"SecurityEventSummary":{"properties":{"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Start of the summarised window (UTC)."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of the summarised window (UTC)."},"interval":{"type":"string","enum":["hour","day"],"title":"Interval","description":"Timeline bucket granularity."},"total":{"type":"integer","title":"Total","description":"Total matching events in the window."},"by_severity":{"$ref":"#/components/schemas/SeverityBreakdown","description":"Severity split across the whole window."},"buckets":{"items":{"$ref":"#/components/schemas/EventTypeBucket"},"type":"array","title":"Buckets","description":"Per-type counts, highest first, capped at the requested number of types."},"timeline":{"items":{"$ref":"#/components/schemas/TimelinePoint"},"type":"array","title":"Timeline","description":"Event volume over time, oldest first."}},"type":"object","required":["window_start","window_end","interval","total","by_severity","buckets","timeline"],"title":"SecurityEventSummary","description":"Aggregates for the security dashboard's charts, bounded by construction:\nthe bucket list is capped and the timeline granularity is fixed."},"SecurityPosture":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this snapshot was computed (UTC)."},"anomalies":{"$ref":"#/components/schemas/AnomalyPosture","description":"Detection state."},"auth":{"$ref":"#/components/schemas/AuthPosture","description":"Authentication pressure and lockouts."},"mfa":{"$ref":"#/components/schemas/MfaPosture","description":"Privileged-account second-factor coverage."},"keys":{"$ref":"#/components/schemas/KeyPosture","description":"NYU key population and churn."},"providers":{"$ref":"#/components/schemas/ProviderPosture","description":"Vendor credential availability."},"vendor_risk":{"$ref":"#/components/schemas/VendorRiskPosture","description":"Supply-chain assessment state."},"containment":{"$ref":"#/components/schemas/ContainmentPosture","description":"Active incident response."},"retention":{"$ref":"#/components/schemas/RetentionPosture","description":"Data lifecycle state."},"events_24h":{"$ref":"#/components/schemas/SeverityBreakdown","description":"Security events recorded in the last 24 hours."},"attention":{"items":{"type":"string"},"type":"array","title":"Attention","description":"Plain-language list of what an operator should look at now, worst first. Empty means the dashboard has nothing to raise."}},"type":"object","required":["generated_at","anomalies","auth","mfa","keys","providers","vendor_risk","containment","retention","events_24h","attention"],"title":"SecurityPosture","description":"One call, one dashboard (CSF DE.AE-06, DE.AE-07, RS.MA-01).\n\nEverything an operator needs to answer \"is anything wrong right now\", in a\nsingle round trip, because a security console that takes nine requests to\nrender is a console nobody opens during an incident."},"SeverityBreakdown":{"properties":{"info":{"type":"integer","title":"Info","description":"Count at severity `info`.","default":0},"low":{"type":"integer","title":"Low","description":"Count at severity `low`.","default":0},"medium":{"type":"integer","title":"Medium","description":"Count at severity `medium`.","default":0},"high":{"type":"integer","title":"High","description":"Count at severity `high`.","default":0},"critical":{"type":"integer","title":"Critical","description":"Count at severity `critical`.","default":0},"total":{"type":"integer","title":"Total","description":"Sum across all severities.","default":0}},"type":"object","title":"SeverityBreakdown","description":"A severity histogram. Present as a fixed shape rather than a free dict so\na dashboard can bind to it without defensive key checks."},"TimelinePoint":{"properties":{"bucket_start":{"type":"string","format":"date-time","title":"Bucket Start","description":"Start of the interval bucket (UTC)."},"total":{"type":"integer","title":"Total","description":"Events recorded in this bucket."},"by_severity":{"$ref":"#/components/schemas/SeverityBreakdown","description":"Severity split within this bucket."}},"type":"object","required":["bucket_start","total","by_severity"],"title":"TimelinePoint","description":"One point of the events-over-time series."},"TimeseriesPoint":{"properties":{"day":{"type":"string","format":"date","title":"Day"},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0},"requests":{"type":"integer","title":"Requests","default":0},"errors":{"type":"integer","title":"Errors","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0}},"type":"object","required":["day"],"title":"TimeseriesPoint"},"TimeseriesSeries":{"properties":{"key":{"type":"string","title":"Key","description":"Provider slug when grouped by provider."},"label":{"type":"string","title":"Label"},"points":{"items":{"$ref":"#/components/schemas/TimeseriesPoint"},"type":"array","title":"Points"}},"type":"object","required":["key","label","points"],"title":"TimeseriesSeries"},"TopUserRow":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Affiliation"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"total_tokens":{"type":"integer","title":"Total Tokens","default":0},"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"requests":{"type":"integer","title":"Requests","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0}},"type":"object","required":["user_id"],"title":"TopUserRow"},"UsageEventOut":{"properties":{"id":{"type":"string","title":"Id"},"request_id":{"type":"string","title":"Request Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"api_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug"},"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id"},"public_alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Alias"},"upstream_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upstream Model"},"endpoint":{"type":"string","title":"Endpoint"},"streamed":{"type":"boolean","title":"Streamed"},"prompt_tokens":{"type":"integer","title":"Prompt Tokens"},"completion_tokens":{"type":"integer","title":"Completion Tokens"},"total_tokens":{"type":"integer","title":"Total Tokens"},"cached_read_tokens":{"type":"integer","title":"Cached Read Tokens"},"cached_write_tokens":{"type":"integer","title":"Cached Write Tokens"},"reasoning_tokens":{"type":"integer","title":"Reasoning Tokens"},"cost_usd":{"type":"number","title":"Cost Usd"},"status":{"type":"string","title":"Status"},"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status"},"error_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Type"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"},"ttft_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttft Ms"},"client_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Ip"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","request_id","endpoint","streamed","prompt_tokens","completion_tokens","total_tokens","cached_read_tokens","cached_write_tokens","reasoning_tokens","cost_usd","status","created_at"],"title":"UsageEventOut"},"UsageSummary":{"properties":{"start":{"type":"string","format":"date","title":"Start"},"end":{"type":"string","format":"date","title":"End"},"source":{"type":"string","enum":["usage_daily","usage_events"],"title":"Source","description":"Which table answered. Single-day windows read the raw log because the rollup lags."},"totals":{"$ref":"#/components/schemas/UsageTotals"},"by_provider":{"items":{"$ref":"#/components/schemas/ProviderUsageRow"},"type":"array","title":"By Provider"},"by_model":{"items":{"$ref":"#/components/schemas/ModelUsageRow"},"type":"array","title":"By Model"}},"type":"object","required":["start","end","source","totals","by_provider","by_model"],"title":"UsageSummary"},"UsageTimeseries":{"properties":{"start":{"type":"string","format":"date","title":"Start"},"end":{"type":"string","format":"date","title":"End"},"granularity":{"type":"string","const":"day","title":"Granularity","default":"day"},"points":{"items":{"$ref":"#/components/schemas/TimeseriesPoint"},"type":"array","title":"Points","description":"Gap-filled: every day in range appears exactly once."},"series":{"items":{"$ref":"#/components/schemas/TimeseriesSeries"},"type":"array","title":"Series","description":"Populated only when `group_by=provider`."}},"type":"object","required":["start","end","points"],"title":"UsageTimeseries"},"UsageTotals":{"properties":{"prompt_tokens":{"type":"integer","title":"Prompt Tokens","default":0},"completion_tokens":{"type":"integer","title":"Completion Tokens","default":0},"total_tokens":{"type":"integer","title":"Total Tokens","default":0},"requests":{"type":"integer","title":"Requests","default":0},"errors":{"type":"integer","title":"Errors","default":0},"cost_usd":{"type":"number","title":"Cost Usd","default":0.0}},"type":"object","title":"UsageTotals"},"UserCreate":{"properties":{"email":{"type":"string","maxLength":320,"minLength":3,"pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$","title":"Email","description":"NYU email. Unique; also the SSO match key when SSO is turned on."},"netid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Netid","description":"NYU NetID, if known."},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"affiliation":{"type":"string","enum":["student","faculty","staff","researcher","affiliate","other"],"title":"Affiliation","default":"other"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"role":{"type":"string","enum":["member","admin","owner"],"title":"Role","default":"member"},"status":{"type":"string","enum":["active","suspended","deprovisioned"],"title":"Status","default":"active"},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id","description":"Omit to let the default policy (or the affiliation auto-assign rule) apply."},"password":{"anyOf":[{"type":"string","minLength":8},{"type":"null"}],"title":"Password","description":"Optional local-login password. Leave null for SSO-only or API-only users."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["email"],"title":"UserCreate"},"UserDataExport":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When the export was produced (UTC)."},"user_id":{"type":"string","title":"User Id","description":"Subject user id."},"email":{"type":"string","title":"Email","description":"Subject email."},"netid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Netid","description":"NetID, if known."},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Name on record."},"affiliation":{"type":"string","title":"Affiliation","description":"Affiliation on record."},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department","description":"Department on record."},"role":{"type":"string","title":"Role","description":"Role in the gateway."},"status":{"type":"string","title":"Status","description":"Account status."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the account was created."},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At","description":"Last sign-in."},"quota_policy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy","description":"Assigned quota tier."},"api_keys":{"items":{"$ref":"#/components/schemas/ExportedKey"},"type":"array","title":"Api Keys","description":"Keys issued to this person, without key material."},"usage":{"items":{"$ref":"#/components/schemas/ExportedUsageRow"},"type":"array","title":"Usage","description":"Aggregated token consumption per model."},"usage_window_days":{"type":"integer","title":"Usage Window Days","description":"How far back the usage aggregation reaches."},"security_events":{"items":{"$ref":"#/components/schemas/SecurityEventOut"},"type":"array","title":"Security Events","description":"Security telemetry naming this person, redacted, most recent first and capped."},"account_locks":{"items":{"$ref":"#/components/schemas/AccountLockOut"},"type":"array","title":"Account Locks","description":"Lockouts recorded against this account."},"containment_actions":{"items":{"$ref":"#/components/schemas/ContainmentOut"},"type":"array","title":"Containment Actions","description":"Containment actions taken against this account."},"not_held":{"items":{"type":"string"},"type":"array","title":"Not Held","description":"Categories of data the gateway does not retain about this person, stated explicitly - prompts and model responses first among them."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written for this export."}},"type":"object","required":["generated_at","user_id","email","affiliation","role","status","created_at","api_keys","usage","usage_window_days","security_events","account_locks","containment_actions","not_held"],"title":"UserDataExport","description":"Everything the gateway holds about one person (CSF PR.DS-10, GV.OC-03).\n\nStates what is held *and* what is not: the absence of prompt content is the\nsingle most important fact in this document, and a reviewer should not have\nto infer it from an empty field."},"UserOut":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"netid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Netid"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"affiliation":{"type":"string","title":"Affiliation"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status"},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id"},"quota_policy_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Name","description":"Denormalised for display; the policy may be unassigned."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"},"active_key_count":{"type":"integer","title":"Active Key Count","description":"Keys currently in `active` status.","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","email","affiliation","role","status","created_at","updated_at"],"title":"UserOut"},"UserPurgeRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":8,"title":"Reason","description":"Why this person's data is being purged. Minimum 8 characters."},"confirm_email":{"type":"string","maxLength":320,"title":"Confirm Email","description":"The target account's email, typed out. Must match exactly or the request is refused."}},"type":"object","required":["reason","confirm_email"],"title":"UserPurgeRequest","description":"Erase a person from the gateway.\n\n`confirm_email` must match the target account exactly. A purge is\nirreversible and the id in the URL is opaque; typing the address is the\ndifference between erasing the right person and erasing a stranger."},"UserPurgeResult":{"properties":{"user_id":{"type":"string","title":"User Id","description":"The purged account's id, which is retained as an opaque handle."},"purged_at":{"type":"string","format":"date-time","title":"Purged At","description":"When the purge ran (UTC)."},"tombstone_email":{"type":"string","title":"Tombstone Email","description":"Non-routable address the account now carries."},"identifiers_cleared":{"items":{"type":"string"},"type":"array","title":"Identifiers Cleared","description":"Identifying columns that were cleared."},"rows_deleted":{"additionalProperties":{"type":"integer"},"type":"object","title":"Rows Deleted","description":"Rows deleted, per table."},"rows_anonymised":{"additionalProperties":{"type":"integer"},"type":"object","title":"Rows Anonymised","description":"Rows de-identified in place, per table."},"retained":{"items":{"type":"string"},"type":"array","title":"Retained","description":"What was deliberately kept, and why - security evidence and billing totals."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Id of the SecurityEvent written."}},"type":"object","required":["user_id","purged_at","tombstone_email","identifiers_cleared","rows_deleted","rows_anonymised","retained"],"title":"UserPurgeResult","description":"What a purge actually did.\n\nThe account row is pseudonymised rather than deleted, on purpose:\n`usage_daily` and `quota_counters` cascade from `users`, so deleting the row\nwould destroy the billing rollups NYU needs to reconcile a vendor invoice,\nand would silently rewrite history. Identifiers are replaced with a\ntombstone; the consumption totals survive attached to a subject nobody can\nre-identify."},"UserQuotaStatus":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id"},"quota_policy_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Name"},"enforcement":{"type":"string","title":"Enforcement","description":"`hard` rejects at the limit; `soft` allows overage and flags it."},"providers":{"items":{"$ref":"#/components/schemas/ProviderQuotaStatus"},"type":"array","title":"Providers"}},"type":"object","required":["user_id","email","enforcement","providers"],"title":"UserQuotaStatus"},"UserUpdate":{"properties":{"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"netid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Netid"},"affiliation":{"anyOf":[{"type":"string","enum":["student","faculty","staff","researcher","affiliate","other"]},{"type":"null"}],"title":"Affiliation"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"role":{"anyOf":[{"type":"string","enum":["member","admin","owner"]},{"type":"null"}],"title":"Role"},"status":{"anyOf":[{"type":"string","enum":["active","suspended","deprovisioned"]},{"type":"null"}],"title":"Status"},"quota_policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Policy Id","description":"Send `null` explicitly to unassign the policy."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"password":{"anyOf":[{"type":"string","minLength":8},{"type":"null"}],"title":"Password","description":"Sets or replaces the local password."}},"type":"object","title":"UserUpdate","description":"PATCH semantics driven by `model_fields_set`: a field that is absent is\nleft alone, and a field explicitly sent as `null` is *cleared*. That is why\nnone of the update models need `clear_x` flags."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VendorAssessmentCreate":{"properties":{"status":{"type":"string","enum":["approved","provisional","under_review","rejected","retired"],"title":"Status","description":"Due-diligence position for this vendor.","default":"under_review"},"data_classification":{"type":"string","enum":["public","internal","sensitive","restricted"],"title":"Data Classification","description":"Highest data classification permitted to reach this vendor. This is the field the whole record exists for: it is the university's written answer to 'may student work go to this company'.","default":"public"},"dpa_in_place":{"type":"boolean","title":"Dpa In Place","description":"A data processing agreement has been executed with this vendor.","default":false},"trains_on_submitted_data":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Trains On Submitted Data","description":"Whether the vendor trains models on submitted data. Null means unknown, which is materially different from 'no' and is reported as a gap."},"data_residency":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Data Residency","description":"Where the vendor processes and stores data."},"subprocessors_url":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Subprocessors Url","description":"Link to the vendor's subprocessor list."},"security_contact":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Security Contact","description":"Vendor security contact for incident notification."},"review_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Review Due At","description":"When this assessment must be re-examined. A vendor past this date is reported as overdue in `/vendor-risk` and on the posture dashboard (CSF GV.SC-07)."},"notes":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}],"title":"Notes","description":"Assessor's notes."},"provider_id":{"type":"string","maxLength":36,"title":"Provider Id","description":"Provider this assessment covers. One assessment per provider."}},"type":"object","required":["provider_id"],"title":"VendorAssessmentCreate"},"VendorAssessmentOut":{"properties":{"status":{"type":"string","enum":["approved","provisional","under_review","rejected","retired"],"title":"Status","description":"Due-diligence position for this vendor.","default":"under_review"},"data_classification":{"type":"string","enum":["public","internal","sensitive","restricted"],"title":"Data Classification","description":"Highest data classification permitted to reach this vendor. This is the field the whole record exists for: it is the university's written answer to 'may student work go to this company'.","default":"public"},"dpa_in_place":{"type":"boolean","title":"Dpa In Place","description":"A data processing agreement has been executed with this vendor.","default":false},"trains_on_submitted_data":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Trains On Submitted Data","description":"Whether the vendor trains models on submitted data. Null means unknown, which is materially different from 'no' and is reported as a gap."},"data_residency":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Data Residency","description":"Where the vendor processes and stores data."},"subprocessors_url":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Subprocessors Url","description":"Link to the vendor's subprocessor list."},"security_contact":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Security Contact","description":"Vendor security contact for incident notification."},"review_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Review Due At","description":"When this assessment must be re-examined. A vendor past this date is reported as overdue in `/vendor-risk` and on the posture dashboard (CSF GV.SC-07)."},"notes":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}],"title":"Notes","description":"Assessor's notes."},"id":{"type":"string","title":"Id","description":"Assessment id."},"provider_id":{"type":"string","title":"Provider Id","description":"Provider covered."},"provider_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Slug","description":"Provider slug, for display."},"provider_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Display Name","description":"Provider name, for display."},"provider_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Provider Enabled","description":"Whether the provider is currently routable. An enabled provider with a rejected or overdue assessment is the combination worth alarming on."},"assessed_by":{"anyOf":[{"$ref":"#/components/schemas/ActorRef"},{"type":"null"}],"description":"Who performed the assessment."},"assessed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Assessed At","description":"When it was performed."},"overdue":{"type":"boolean","title":"Overdue","description":"True when `review_due_at` is in the past."},"days_until_review":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Review","description":"Days until review is due; negative when overdue."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation time."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last modification time."}},"type":"object","required":["id","provider_id","overdue","created_at","updated_at"],"title":"VendorAssessmentOut","description":"A third-party risk record (CSF GV.SC-04, GV.SC-07, ID.AM-04)."},"VendorAssessmentUpdate":{"properties":{"status":{"anyOf":[{"type":"string","enum":["approved","provisional","under_review","rejected","retired"]},{"type":"null"}],"title":"Status"},"data_classification":{"anyOf":[{"type":"string","enum":["public","internal","sensitive","restricted"]},{"type":"null"}],"title":"Data Classification"},"dpa_in_place":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dpa In Place"},"trains_on_submitted_data":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Trains On Submitted Data"},"data_residency":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Data Residency"},"subprocessors_url":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Subprocessors Url"},"security_contact":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Security Contact"},"review_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Review Due At"},"notes":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}],"title":"Notes"}},"type":"object","title":"VendorAssessmentUpdate","description":"Every field optional; omitted fields are left untouched."},"VendorRiskPosture":{"properties":{"providers_total":{"type":"integer","title":"Providers Total","description":"Providers configured."},"assessed":{"type":"integer","title":"Assessed","description":"Providers with an assessment."},"unassessed":{"type":"integer","title":"Unassessed","description":"Providers with none."},"overdue_reviews":{"type":"integer","title":"Overdue Reviews","description":"Assessments past their review date (CSF GV.SC-07)."},"overdue":{"items":{"$ref":"#/components/schemas/VendorRiskRow"},"type":"array","title":"Overdue","description":"Those overdue vendors, capped."},"enabled_without_approval":{"type":"integer","title":"Enabled Without Approval","description":"Providers the gateway will route to whose assessment is not approved or provisional - live traffic to an unvetted vendor."}},"type":"object","required":["providers_total","assessed","unassessed","overdue_reviews","overdue","enabled_without_approval"],"title":"VendorRiskPosture"},"VendorRiskRow":{"properties":{"provider_id":{"type":"string","title":"Provider Id","description":"Provider id."},"slug":{"type":"string","title":"Slug","description":"Provider slug."},"display_name":{"type":"string","title":"Display Name","description":"Provider name."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the gateway will currently route to this vendor."},"has_assessment":{"type":"boolean","title":"Has Assessment","description":"False when no due-diligence record exists."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Assessment status, if assessed."},"data_classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Classification","description":"Highest classification approved for this vendor."},"dpa_in_place":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dpa In Place","description":"DPA executed."},"trains_on_submitted_data":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Trains On Submitted Data","description":"Vendor trains on submitted data; null means unknown."},"review_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Review Due At","description":"Next review date."},"overdue":{"type":"boolean","title":"Overdue","description":"Review date has passed."},"concerns":{"items":{"type":"string"},"type":"array","title":"Concerns","description":"Plain-language findings for this vendor, e.g. 'enabled but never assessed'."}},"type":"object","required":["provider_id","slug","display_name","enabled","has_assessment","overdue"],"title":"VendorRiskRow","description":"One provider's risk line, including providers with no assessment at all -\nwhich is the row a reviewer most needs to see."},"VendorRiskSummary":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this summary was computed (UTC)."},"providers_total":{"type":"integer","title":"Providers Total","description":"Providers configured."},"providers_enabled":{"type":"integer","title":"Providers Enabled","description":"Providers currently routable."},"assessed":{"type":"integer","title":"Assessed","description":"Providers with an assessment record."},"unassessed":{"type":"integer","title":"Unassessed","description":"Providers with no assessment record at all."},"overdue_reviews":{"type":"integer","title":"Overdue Reviews","description":"Assessments past their review date."},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status","description":"Assessment count per status."},"approved_by_classification":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Approved By Classification","description":"Classification -> provider slugs approved to receive data at that level. Only `approved` and `provisional` assessments appear here."},"rows":{"items":{"$ref":"#/components/schemas/VendorRiskRow"},"type":"array","title":"Rows","description":"Every provider, assessed or not, ordered worst-first."}},"type":"object","required":["generated_at","providers_total","providers_enabled","assessed","unassessed","overdue_reviews","by_status","approved_by_classification","rows"],"title":"VendorRiskSummary","description":"The supply-chain view: which vendors are approved for what, and which\nreviews have lapsed (CSF GV.SC-04, GV.SC-07, GV.SC-09)."}}}}