{ "openapi": "3.1.0", "info": { "title": "Philter API", "version": "4.0.0-SNAPSHOT" }, "servers": [ { "description": "Philter API", "url": "/" } ], "paths": { "/api/api-keys": { "get": { "tags": [ "API Keys" ], "summary": "List the calling key's user's API keys.", "description": "Lists the active keys belonging to the calling key's user, oldest first, with the total. Returns each key's ID, prefix, scopes, creation time, and whether it is the bootstrap key, never the key itself. session=true lists only session keys and session=false only long-lived keys; total counts only the keys listed. Requires the `api-keys:read` scope.", "operationId": "getApiKeys", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } }, { "description": "true lists only session keys, false only long-lived keys; left out, both. total counts only the keys listed.", "name": "session", "in": "query", "required": false, "schema": { "type": "boolean" } } ], "responses": { "200": { "description": "A page of keys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetApiKeysResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:read.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "API Keys" ], "summary": "Create an API key for the calling key's user.", "description": "Mints a key for the calling key's user, so a key can be rotated without an administrator. The scopes must be a subset of those the calling key holds. The key is returned once: only its hash is stored. Recorded as an api_key_created audit event naming the calling user and API key. Requires the `api-keys:write` scope.", "operationId": "createOwnApiKey", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateApiKeyRequest" } } } }, "responses": { "201": { "description": "The key was created. The value is returned here and nowhere else.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatedApiKeyResponse" } } } }, "400": { "description": "No scopes were given, or one of them is not a scope.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:write, is a session key, or a requested scope is not held by the calling key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/api-keys/current": { "delete": { "tags": [ "API Keys" ], "summary": "Sign out: revoke the calling session key.", "description": "Revokes the session key making the request, so a person can sign out without an administrator. Any key may call it, whatever its scopes, because it can only end the caller's own access. A long-lived key cannot revoke itself; revoke it with another key. Recorded as an api_key_deleted audit event with the reason signed out.", "operationId": "signOut", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The session key was revoked." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The calling key is a long-lived key, not a session key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/api-keys/scopes": { "get": { "tags": [ "API Keys" ], "summary": "List the API key scopes.", "description": "Returns every scope an API key can carry, with what it allows, in the order Philter declares them, so a client offering scopes to choose from need not hard-code them. Any key may call it, whatever its scopes, since it describes the API rather than any account.", "operationId": "getScopes", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Every scope.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiKeyScopesResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/api-keys/{keyId}": { "delete": { "tags": [ "API Keys" ], "summary": "Revoke an API key.", "description": "Revokes a key: it stops authenticating at once and cannot be restored. The key making the request cannot revoke itself; revoke it with another key. The calling key cannot revoke a key holding a scope it does not hold. A caller can revoke its own user's keys; an administrator can revoke any user's. Recorded as an api_key_deleted audit event naming the calling user and API key. Requires the `api-keys:write` scope.", "operationId": "revokeApiKey", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The key was revoked." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:write, or the key being revoked holds a scope the calling key does not.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no active key with that ID that the caller may manage." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The key is the one making the request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/api-keys/{keyId}/scopes": { "put": { "tags": [ "API Keys" ], "summary": "Replace an API key's scopes.", "description": "Replaces the scopes on a key. The key value does not change. The new scopes must be a subset of those the calling key holds, and the calling key cannot change a key holding a scope it does not hold. The key making the request cannot change its own scopes. A caller can change its own user's keys; an administrator can change any user's. Recorded as an api_key_scopes_changed audit event with the scopes before and after and the calling user and API key. Requires the `api-keys:write` scope.", "operationId": "setApiKeyScopes", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetApiKeyScopesRequest" } } } }, "responses": { "200": { "description": "The scopes were replaced.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiKeyResponse" } } } }, "400": { "description": "No scopes were given, or one of them is not a scope.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:write, a requested scope is not held by the calling key, the key being changed holds a scope the calling key does not, or the calling key is a session key and the change adds a scope.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no active key with that ID that the caller may manage." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The key is the one making the request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/audit": { "get": { "tags": [ "Audit Log" ], "summary": "List audit events.", "description": "Returns audit events, most recent first, paginated. Filter by event type with event, by time with from and to (ISO-8601 instants; from is inclusive, to is exclusive), and by acting principal with owner. Reading the audit log is itself audited. The log covers the whole deployment, so this requires an administrator as well as the audit:read scope. Requires the `audit:read` scope.", "operationId": "getAuditLog", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "event", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "from", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "to", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetAuditResponse" } } } }, "400": { "description": "A timestamp could not be parsed, the range is reversed, or the event type is not one Philter emits.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold audit:read, or the caller is not an administrator.", "content": { "application/json": { "schema": { "type": "string" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/audit/export": { "get": { "tags": [ "Audit Log" ], "summary": "Export the audit log as CSV.", "description": "Returns the audit log for a range of whole days as CSV, most recent first. from and to are dates (YYYY-MM-DD), both inclusive, read in zone (an IANA time zone such as UTC or America/New_York), or in the server's time zone when zone is omitted. to may be at most 30 days after from. Returns up to limit events (default 100, at most 1000; a larger value is treated as the maximum, and zero or less as the default), starting offset events into the range; the X-Philter-Export-Truncated header is true when more remain, and X-Philter-Export-Next-Offset gives the offset to request next. Events are ordered by timestamp and then id, so paging a range that is no longer receiving events returns each exactly once; a range that includes the current day can repeat events across pages. Timestamps in the CSV are UTC. The export is itself audited. Requires an administrator as well as the audit:read scope. Requires the `audit:read` scope.", "operationId": "exportAuditLog", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "from", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "to", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "zone", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 100 } } ], "responses": { "200": { "description": "The CSV. Headers give the row count, whether it was truncated, and the time zone used.", "content": { "text/csv": { "schema": { "type": "object" } } } }, "400": { "description": "A date is missing or not YYYY-MM-DD, the range is reversed or longer than the maximum, the zone is not a time zone, offset is negative or not a number, or limit is not a number. The body says which.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold audit:read, or the caller is not an administrator.", "content": { "text/csv": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts": { "delete": { "tags": [ "Contexts" ], "summary": "Delete a context by name in the query.", "description": "Deletes a context named in the name query parameter, for a context whose name cannot be used in a request path. Otherwise the same as DELETE /api/contexts/{name}. Requires the `contexts:write` scope.", "operationId": "deleteContextNamedInQuery", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The context was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no context with that name, or the owner does not exist or the caller may not reach it. The body carries a message.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The context has open asynchronous redaction jobs and cannot be deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Contexts" ], "summary": "Get the names of existing contexts.", "description": "Get the names of the caller's contexts, paged. Admins may list another user's contexts with owner, or every user's with all_users=true, which returns each context's name and owner and requires ADMIN_CROSS_USER_ACCESS_ENABLED. Requires the `contexts:read` scope.", "operationId": "getContexts", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "all_users", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "Context names; with all_users, each context's name and owner.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/GetContextsResponse" }, { "$ref": "#/components/schemas/GetAllUsersContextsResponse" } ] } } } }, "400": { "description": "Both owner and all_users were given.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it; or all_users was given by a caller who is not an administrator, or with cross-user access disabled.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Contexts" ], "summary": "Create a context.", "description": "Creates a context. A user can have at most 10 contexts, counting default. Requires the `contexts:write` scope.", "operationId": "createContext", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "entity_type_disambiguation", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "description": "Whether span disambiguation learns within each document (document) or across every document redacted in the context (context). Applies when entity_type_disambiguation is on.", "name": "disambiguation_scope", "in": "query", "required": false, "schema": { "type": "string", "default": "document", "enum": [ "document", "context" ] } }, { "name": "ledger", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The context was created.", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "description": "The name is missing or blank, or cannot contain /, \\, ;, %, or control characters, and cannot be . or .., since it must be usable in a request path; or disambiguation_scope is not document or context.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The context was not created, and reason says why: context_exists (the caller already has a context with this name) or context_limit_reached (the caller already has the most contexts a user may have).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContextConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts/{name}": { "delete": { "tags": [ "Contexts" ], "summary": "Delete a context.", "description": "Delete an existing context. Requires the `contexts:write` scope.", "operationId": "deleteContext", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The context was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no context with that name, or the owner does not exist or the caller may not reach it. The body carries a message.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The context has open asynchronous redaction jobs and cannot be deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Contexts" ], "summary": "Get the details of a context.", "description": "Get the details of a context with the provided name: its size and its entries counted by filter type. The counts are computed in the database and sum to the size, with entries that have no filter type counted as untyped. Requires the `contexts:read` scope.", "operationId": "getContext", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetContextResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "A context with the given name does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Contexts" ], "summary": "Update a context's settings.", "description": "Changes a context's entity_type_disambiguation, disambiguation_scope, and ledger settings. Only the settings given change; one left out keeps its current value. Recorded as a context_updated audit event naming each setting that changed. Requires the `contexts:write` scope.", "operationId": "updateContext", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "description": "Whether to apply entity-type span disambiguation. Left out, it keeps its current value.", "name": "entity_type_disambiguation", "in": "query", "required": false, "schema": { "type": "boolean" } }, { "description": "Whether span disambiguation learns within each document (document) or across every document redacted in the context (context). Left out, it keeps its current value.", "name": "disambiguation_scope", "in": "query", "required": false, "schema": { "type": "string", "enum": [ "document", "context" ] } }, { "description": "Whether to record redactions in the ledger. Left out, it keeps its current value.", "name": "ledger", "in": "query", "required": false, "schema": { "type": "boolean" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The context was updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "No setting was given, or disambiguation_scope is not document or context.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Context not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts/{name}/entries": { "delete": { "tags": [ "Contexts" ], "summary": "Empty all entries from a context.", "description": "Requires the `contexts:write` scope.", "operationId": "emptyEntries", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Not Found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Contexts" ], "summary": "List entries within a context.", "description": "Requires the `contexts:read` scope.", "operationId": "listEntries", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetContextEntriesResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Context not found.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts/{name}/entries/export": { "get": { "tags": [ "Contexts" ], "summary": "Export a context's mapping table.", "description": "Returns the complete token-to-replacement mapping table for a context in a portable JSON form that can be re-imported into another context or environment. Only token hashes (never the original tokens) and their replacements are returned. Requires the `contexts:read` scope.", "operationId": "exportEntries", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContextEntriesExport" } } }, "headers": { "Content-Disposition": { "schema": { "type": "string" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Context not found.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts/{name}/entries/import": { "post": { "tags": [ "Contexts" ], "summary": "Import a mapping table into a context.", "description": "Imports token-to-replacement mappings (as produced by the export endpoint) into a context. By default an incoming token that already exists in the context is skipped; pass on_conflict=overwrite to replace existing replacements instead. Requires the `contexts:write` scope.", "operationId": "importEntries", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "on_conflict", "in": "query", "required": false, "schema": { "type": "string", "default": "skip" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContextEntriesExport" } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportContextEntriesResponse" } } } }, "400": { "description": "The payload or on_conflict value is invalid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Context not found.", "content": { "application/json": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/contexts/{name}/entries/{entryId}": { "delete": { "tags": [ "Contexts" ], "summary": "Delete a single context entry by id.", "description": "Requires the `contexts:write` scope.", "operationId": "deleteEntry", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "entryId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Not Found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/documents": { "get": { "tags": [ "Documents" ], "summary": "List documents submitted for async redaction.", "description": "Requires the `documents:read` scope.", "operationId": "listDocuments", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetDocumentsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Owner unavailable or inaccessible." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/documents/{documentId}": { "delete": { "tags": [ "Documents" ], "summary": "Delete an async redaction record.", "description": "Requires the `documents:write` scope.", "operationId": "deleteDocument", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The record and any stored redacted bytes were deleted." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Unknown document id." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "Notification reconciliation is pending.", "content": { "text/plain": { "schema": { "type": "string" } } }, "headers": { "Retry-After": { "schema": { "example": 5, "type": "integer", "format": "int32" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Documents" ], "summary": "Download the redacted document.", "description": "Requires the `documents:read` scope.", "operationId": "downloadDocument", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Completed result bytes.", "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } }, "application/zip": { "schema": { "type": "string", "format": "binary" } } }, "headers": { "Content-Disposition": { "schema": { "type": "string" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Unknown document id.", "content": { "*/*": { "schema": { "type": "string", "format": "byte" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "Redaction not yet complete.", "content": { "*/*": { "schema": { "type": "string", "format": "byte" } } } }, "410": { "description": "Redaction failed.", "content": { "*/*": { "schema": { "type": "string", "format": "byte" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/documents/{documentId}/status": { "get": { "tags": [ "Documents" ], "summary": "Get the status of an async redaction.", "description": "Requires the `documents:read` scope.", "operationId": "getStatus", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetRedactionStatusResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Unknown document id.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/explain": { "post": { "tags": [ "Explain" ], "summary": "Redact text and explain the redactions.", "description": "Redacts the plain-text request body using the given policy and context, returning the filtered text together with an explanation of every span that was redacted (type, position, and applied filter). Use the c query parameter to select a context and p to select a policy. When output signing is enabled in Admin Settings, the response includes an `X-Philter-Signature` header containing an ES256 JWT that binds the SHA-256 hash of the response body, the applied policy name and version, a per-response UUID, and an issue timestamp. Verify the signature using the public key from `GET /api/signing-key`. Pass sign=true to request a signature when the deployment has not enabled signing. The admin setting is a floor: with it on every response is signed and sign=false does not change that. Requires the `redact` scope.", "operationId": "explainTextPlainAsApplicationJson", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "c", "in": "query", "required": false, "schema": { "type": "string", "default": "" } }, { "name": "p", "in": "query", "required": false, "schema": { "type": "string", "default": "default" } }, { "name": "filename", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "sign", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } } ], "requestBody": { "required": true, "content": { "text/plain": { "schema": { "type": "string" } } } }, "responses": { "200": { "description": "Full text filter result with governing policy metadata.", "content": { "application/json": { "schema": { "type": "object", "properties": { "context": { "type": "string" }, "explanation": { "type": "object", "additionalProperties": true }, "filteredText": { "type": "string" }, "policyContentHash": { "type": "string" }, "policyName": { "type": "string" }, "policyVersion": { "type": "integer", "format": "int32" } }, "additionalProperties": true } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Not Found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "405": { "description": "Method Not Allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "Not Acceptable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Signing is enabled but the signing operation failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/filter": { "post": { "tags": [ "Filter" ], "summary": "Redact text or a PDF.", "description": "Requires the `redact` scope. Text is synchronous and returns text/plain. For PDFs select application/pdf or application/zip with Accept. PDFs default to async=true, returning application/json with documentId regardless of the selected download format. Set async=false for inline binary output. Async submission captures resolved configuration. Pass sign=true to request a signature when the deployment has not enabled signing. The admin setting is a floor: with it on every response is signed and sign=false does not change that. It applies to the text path, which is the one that is signed; sign=true on a PDF request is refused with 400 rather than answered unsigned.", "operationId": "filter", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "c", "in": "query", "required": false, "schema": { "type": "string", "default": "" } }, { "name": "p", "in": "query", "required": false, "schema": { "type": "string", "default": "default" } }, { "name": "filename", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "sign", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "async", "in": "query", "required": false, "schema": { "type": "boolean", "default": true } } ], "requestBody": { "required": true, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } }, "text/plain": { "schema": { "type": "string" } } } }, "responses": { "200": { "description": "Redacted text or synchronous binary result.", "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } }, "application/zip": { "schema": { "type": "string", "format": "binary" } }, "text/plain": { "schema": { "type": "string" } } }, "headers": { "X-Document-Id": { "schema": { "type": "string" } }, "X-Philter-Policy-Hash": { "schema": { "type": "string" } }, "X-Philter-Policy-Name": { "schema": { "type": "string" } }, "X-Philter-Policy-Version": { "schema": { "type": "string" } } } }, "202": { "description": "PDF accepted; poll status and download the result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "documentId": { "type": "string" } } } } }, "headers": { "Location": { "schema": { "type": "string" } }, "X-Effective-Configuration-SHA256": { "schema": { "type": "string" } }, "X-Philter-Policy-Hash": { "schema": { "type": "string" } }, "X-Philter-Policy-Name": { "schema": { "type": "string" } }, "X-Philter-Policy-Version": { "schema": { "type": "string" } } } }, "400": { "description": "Invalid request, malformed or unparseable PDF, password-protected PDF, PDF without pages, or sign=true on a PDF request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The named policy does not exist.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "405": { "description": "Method Not Allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "Not Acceptable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Request body, resolved configuration (1 MiB), or encrypted async record exceeds its size limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "429": { "description": "Account async queue capacity reached.", "content": { "text/plain": { "schema": { "type": "string" } } }, "headers": { "Retry-After": { "schema": { "example": 5, "type": "integer", "format": "int32" } } } }, "500": { "description": "Signing is enabled but the signing operation failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Global queue capacity reached, admission contention wait (500 ms) exhausted, or recovery required.", "content": { "text/plain": { "schema": { "type": "string" } } }, "headers": { "Retry-After": { "schema": { "example": 5, "type": "integer", "format": "int32" } } } } } } }, "/api/health": { "get": { "security": [], "tags": [ "Health" ], "summary": "Get the health of Philter, including the supported redaction policy schema version.", "description": "Unauthenticated endpoint returning health, the application version, the supported redaction policy schema version, and the build's git commit. The status is \"UP\" when Philter is healthy.", "operationId": "status", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/holds": { "delete": { "tags": [ "Legal Holds" ], "summary": "Release a legal hold by reference in the query.", "description": "Releases the hold whose reference is given in the reference query parameter, for a hold whose reference cannot be used in a request path. Otherwise the same as DELETE /api/holds/{reference}, including the audit record. Requires the `holds:write` scope.", "operationId": "releaseHoldNamedInQuery", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "reference", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The hold was released." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "No hold with the given reference exists for this user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The hold was not released because an evidence or hold operation for the owner is active or requires recovery; reason is operation_in_progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Legal Holds" ], "summary": "List legal holds.", "description": "Returns the caller's active legal holds, paged and ordered by set date descending. Admins may list another user's holds via the owner parameter, or every user's with all_users=true, which adds each hold's owner and requires ADMIN_CROSS_USER_ACCESS_ENABLED. Requires the `holds:read` scope.", "operationId": "listHolds", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "all_users", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "Array of legal holds, most recently set first. With all_users, each hold also has an owner field.", "content": { "application/json": { "schema": { "oneOf": [ { "type": "array", "items": { "$ref": "#/components/schemas/LegalHoldResponse" } }, { "type": "array", "items": { "$ref": "#/components/schemas/OwnedLegalHoldResponse" } } ] } } } }, "400": { "description": "Both owner and all_users were given.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/LegalHoldResponse" } } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Legal Holds" ], "summary": "Set a legal hold.", "description": "Creates a named hold that blocks deletion and purge of the specified evidence until the hold is released. The reference must be unique for the calling user. Two scope types are supported: 'document_chain' protects a specific document's ledger chain; 'user' protects all governance evidence owned by the hold's owner, which is the caller or the user named by owner. For a user hold, scopeValue is optional; if given it must be the owner's username, and the owner's username is stored either way. Admins may place a hold on another user's evidence via the owner parameter. Requires the `holds:write` scope.", "operationId": "setHold", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldRequest" } } } }, "responses": { "201": { "description": "The hold was set and is now active.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldResponse" } } } }, "400": { "description": "Required fields are missing, the scope type is invalid, or the reference cannot contain /, \\, ;, %, or control characters, and cannot be . or .., since it must be usable in a request path.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The hold was not set, and reason says why: hold_exists (a hold with this reference exists) or operation_in_progress (an evidence or hold operation for the owner is active or requires recovery).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/holds/{reference}": { "delete": { "tags": [ "Legal Holds" ], "summary": "Release a legal hold.", "description": "Removes the hold with the given reference. Once released, evidence previously covered by this hold may become eligible for deletion or purge if no other holds remain. Releasing a hold is audited. Admins may release another user's hold via the owner parameter. Requires the `holds:write` scope.", "operationId": "releaseHold", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "reference", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The hold was released." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "No hold with the given reference exists for this user. When the owner was reached, the body carries a message; an unreachable owner returns no body, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The hold was not released because an evidence or hold operation for the owner is active or requires recovery; reason is operation_in_progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Legal Holds" ], "summary": "Get a legal hold.", "description": "Returns the hold with the given reference. Admins may retrieve another user's hold via the owner parameter. Requires the `holds:read` scope.", "operationId": "getHold", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "reference", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The hold details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "No hold with the given reference exists for this user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LegalHoldResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/ledger": { "delete": { "tags": [ "Redaction Ledger" ], "summary": "Purge old ledger entries.", "description": "Deletes whole completed chains whose completion and newest entry are older than the given number of days. The ledger is kept indefinitely by default, so this is how stale entries are pruned on demand. Requires an administrator and LEDGER_DELETION_ENABLED=true. Admins may purge another user's entries by passing that user's username as owner, which additionally requires ADMIN_CROSS_USER_ACCESS_ENABLED=true. Requires the `ledger:delete` scope.", "operationId": "purge", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "older_than_days", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The matching entries were purged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "older_than_days is missing or negative.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The caller is not an administrator, or ledger deletion is disabled for this deployment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "Not Found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "An evidence or hold operation is active or requires recovery.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "423": { "description": "One or more active legal holds protect entries in this user's ledger. Release all holds before purging.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Redaction Ledger" ], "summary": "List redaction-ledger chains.", "description": "Returns the head (genesis entry) of each redacted document's ledger chain, most recent first. Pass q to filter by document id or filename. Admins may list another user's chains by passing that user's username as owner, or every user's with all_users=true, which adds each chain's owner, cannot be combined with q, and requires ADMIN_CROSS_USER_ACCESS_ENABLED. Requires the `ledger:read` scope.", "operationId": "getLedger", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "q", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "all_users", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "Ledger chain heads; with all_users, each also names its owner.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/GetLedgerResponse" }, { "type": "object", "properties": { "chains": { "type": "array", "items": { "$ref": "#/components/schemas/OwnedLedgerEntryView" } }, "total": { "type": "integer", "format": "int32" } } } ] } } } }, "400": { "description": "all_users was combined with owner or q.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/ledger/{documentId}": { "delete": { "tags": [ "Redaction Ledger" ], "summary": "Delete a document's ledger chain.", "description": "Permanently deletes every ledger entry for a completed document chain. Active or failed publication returns 409. Requires an administrator and LEDGER_DELETION_ENABLED=true. Admins may delete another user's chain by passing that user's username as owner, which additionally requires ADMIN_CROSS_USER_ACCESS_ENABLED=true. Requires the `ledger:delete` scope.", "operationId": "deleteChain", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The document's ledger chain was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The caller is not an administrator, or ledger deletion is disabled for this deployment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "No ledger chain for that document exists for this user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "An evidence or hold operation is active or requires recovery.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "423": { "description": "The chain is protected by an active legal hold. Release the hold before deleting.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Redaction Ledger" ], "summary": "Get a document's ledger chain.", "description": "Returns the full ordered chain of ledger entries for a document, along with whether the hash chain currently verifies. A chain that cannot be validated, such as one with an entry that can no longer be read, returns valid false with a validationError and no entries. Requires the `ledger:read` scope.", "operationId": "getChain", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LedgerChainResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "405": { "description": "Method Not Allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "Not Acceptable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/ledger/{documentId}/export": { "get": { "tags": [ "Redaction Ledger" ], "summary": "Export a document's ledger chain.", "description": "Returns the full ledger chain for a document as a portable JSON document that can be archived and later re-verified. The export contains the decrypted token and replacement values, so treat it as sensitive. A chain with an entry that cannot be read is not exported, even in part. Requires the `ledger:export` scope.", "operationId": "exportChain", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LedgerExport" } } }, "headers": { "Content-Disposition": { "schema": { "type": "string" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Not Found", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "422": { "description": "An entry in the chain could not be read, so the chain is not exported; reason is entry_unreadable. The attempt is audited as redaction_ledger_exported with refused in its details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LedgerRefusalResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/ledger/{documentId}/valid": { "get": { "tags": [ "Redaction Ledger" ], "summary": "Verify a document's ledger chain.", "description": "Returns whether the hash chain for the document's ledger verifies (no entry has been altered and every link is intact). A chain that cannot be validated returns valid false with a validationError. Requires the `ledger:read` scope.", "operationId": "validateChain", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "documentId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LedgerChainResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "405": { "description": "Method Not Allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "Not Acceptable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/limits": { "get": { "tags": [ "Limits" ], "summary": "Get Philter's limits and the caller's capabilities.", "description": "Returns the sizes, lengths, and name rules Philter enforces, the session key timeouts, and what the caller may do: their role, how many contexts they have, and whether they may reach other users' resources or delete ledger chains. Each value is the one Philter enforces, including any set through its environment variable. Any key may call it, whatever its scopes, including a session key that must first change its password or enroll in MFA, since it describes the API and the caller's own access. For an administrator, crossUserAccess and ledgerDeletion reflect ADMIN_CROSS_USER_ACCESS_ENABLED and LEDGER_DELETION_ENABLED; for anyone else they are false, so the response does not reveal those settings.", "operationId": "getLimits", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The limits and the caller's capabilities.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LimitsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/lists": { "delete": { "tags": [ "Custom Lists" ], "summary": "Delete a list by name in the query.", "description": "Deletes a list named in the name query parameter, for a list whose name cannot be used in a request path. Otherwise the same as DELETE /api/lists/{name}. Requires the `lists:write` scope.", "operationId": "deleteListNamedInQuery", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The list was deleted.", "content": { "application/json": { "schema": { "type": "string" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The given list does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Custom Lists" ], "summary": "List custom lists.", "description": "Lists the caller's custom lists, all of them, each with its name, description, and number of items. Admins may list another user's lists with owner, or every user's with all_users=true, which is paged with offset and limit, adds each list's owner, and requires ADMIN_CROSS_USER_ACCESS_ENABLED. Requires the `lists:read` scope.", "operationId": "getLists_1", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "all_users", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "Each list's name, description, and size; with all_users, its owner too.", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ListSummaryResponse" } } } } }, "400": { "description": "Both owner and all_users were given.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts. Also returned for all_users when the caller is not an administrator or cross-user access is disabled.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/lists/{name}": { "delete": { "tags": [ "Custom Lists" ], "summary": "Delete a list.", "description": "Delete a list with the given name. Requires the `lists:write` scope.", "operationId": "deleteList", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The list was deleted.", "content": { "application/json": { "schema": { "type": "string" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The given list does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Custom Lists" ], "summary": "Get the contents of a list.", "description": "Gets a list's items, in the lists field, and its description. Requires the `lists:read` scope.", "operationId": "getLists", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetListsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The list does not exist, or the owner does not exist or may not be reached. The API does not distinguish these, so a name or owner cannot be used to discover what exists.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetListsResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Custom Lists" ], "summary": "Create a list.", "description": "Creates a list whose items are the request body. A name the owner already uses is refused with 409; replace an existing list with PUT /api/lists/{name}. description is optional and defaults to an empty string. Requires the `lists:write` scope.", "operationId": "createList", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "description": "The list's description. Defaults to an empty string.", "name": "description", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }, "responses": { "201": { "description": "The list was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "The list name is empty or cannot contain /, \\, ;, %, or control characters, and cannot be . or .., the list contains too many items (maximum 100), or an item is too long (maximum 50 characters).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The list was not created because the owner already has a list with this name; reason is list_exists. Nothing is changed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomListConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Custom Lists" ], "summary": "Replace a list.", "description": "Replaces the items of an existing list with the request body. description is optional: left out, the list keeps its description; given as an empty string, it is cleared. Requires the `lists:write` scope.", "operationId": "replaceList", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }, { "description": "The list's description. Left out, the list keeps its own; an empty string clears it.", "name": "description", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }, "responses": { "200": { "description": "The list was replaced.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "The list contains too many items (maximum 100), or an item is too long (maximum 50 characters).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such list, or the owner does not exist or the caller may not reach it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies": { "get": { "tags": [ "Policies" ], "summary": "Get the names of existing policies.", "description": "Returns the names of the caller's policies, paged. Admins may list another user's policies by passing that user's username as owner, or every user's with all_users=true, which returns each policy's name and owner and requires ADMIN_CROSS_USER_ACCESS_ENABLED. With managed=true, returns the built-in managed policies instead, each with its name and description. With deleted=true, returns the caller's (or owner's) deleted policies whose version history is kept, by name, each with its latest revision and when and by whom it was deleted; read a deleted policy's history with GET /api/policies/{policyName}/versions. Requires the `policies:read` scope.", "operationId": "getPolicyNames", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "all_users", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "managed", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "deleted", "in": "query", "required": false, "schema": { "type": "boolean", "default": false } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "The names of the policies; with all_users, objects naming each policy and its owner; with managed, objects giving each managed policy's name and description; with deleted, objects describing each deleted policy.", "content": { "application/json": { "schema": { "oneOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "array", "items": { "$ref": "#/components/schemas/OwnedNameResponse" } }, { "type": "array", "items": { "$ref": "#/components/schemas/ManagedPolicySummary" } }, { "type": "array", "items": { "$ref": "#/components/schemas/DeletedPolicySummary" } } ] } } } }, "400": { "description": "Both owner and all_users were given, managed was combined with either, or deleted was combined with all_users or managed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Policies" ], "summary": "Create a policy.", "description": "Creates a policy from the request body under the given name. A name the owner already uses is refused with 409; replace an existing policy with PUT /api/policies/{policyName}. The policy is validated before it is stored. Set its description and notes afterwards with PUT /api/policies/{policyName}/details. Admins may create a policy in another user's account by passing that user's username as owner. Requires the `policies:write` scope.", "operationId": "create", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "description": "Native Phileas policy. identifiers is required; custom dictionaries use dictionaries.", "type": "object", "additionalProperties": true } } } }, "responses": { "201": { "description": "The policy was created and is now active. A policy_activated audit event is recorded.", "content": { "*/*": { "schema": { "type": "object" } } } }, "400": { "description": "The policy name is missing or invalid, the policy is invalid, or the request has a description or notes parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The policy was not created because the owner already has a policy with this name; reason is policy_exists. Nothing is changed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/compile": { "post": { "tags": [ "Policies" ], "summary": "Compile a PhiSQL policy.", "description": "Compiles a policy authored in PhiSQL into the native Phileas policy format. The request body is PhiSQL source; the response carries the policy name and description and the compiled policy JSON, which can then be saved via POST /api/policies. Requires the `policies:read` scope.", "operationId": "compile", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "text/plain": { "schema": { "type": "string" } } } }, "responses": { "200": { "description": "Compiled native policy; does not save it.", "content": { "application/json": { "schema": { "type": "object", "properties": { "description": { "type": "string" }, "name": { "type": "string" }, "policy": { "type": "object", "additionalProperties": true } } } } } }, "400": { "description": "The PhiSQL failed to parse/compile, or the compiled policy failed validation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/templates/{templateName}": { "get": { "tags": [ "Policies" ], "summary": "Get a policy template.", "description": "Returns a starting point for a new policy: native policy JSON that Philter accepts for the running policy schema version, to edit and save with POST /api/policies. The template named default is the policy every new user's default policy is created from. A template is not a policy and cannot be used to redact until it is saved as one. Requires the `policies:read` scope.", "operationId": "getTemplate", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "templateName", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The template's native policy JSON.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no template with the given name.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}": { "delete": { "tags": [ "Policies" ], "summary": "Delete a policy.", "description": "Deletes the policy with the given name. Admins may delete another user's policy by passing that user's username as owner. Requires the `policies:write` scope.", "operationId": "delete", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The policy was deleted." }, "400": { "description": "The policy name is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such policy, with a message. Also returned, with no body, when the owner does not exist or the caller may not reach it, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The policy was not deleted, and reason says why: policy_default (the default policy) or policy_managed (a managed policy).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Policies" ], "summary": "Get a policy.", "description": "Returns the full policy with the given name. A name starting with managed_ returns that built-in managed policy. Admins may retrieve another user's policy by passing that user's username as owner. The policy's description and notes are at /api/policies/{policyName}/details. Requires the `policies:read` scope.", "operationId": "get", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Native policy JSON, preserving field names.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "400": { "description": "The policy name is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "A policy with the given name does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Policies" ], "summary": "Replace a policy.", "description": "Replaces an existing policy with the request body, as a new revision. The policy is validated before it is stored. Its description and notes are kept; change them with PUT /api/policies/{policyName}/details. Admins may replace another user's policy by passing that user's username as owner. Requires the `policies:write` scope.", "operationId": "replace", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "description": "Native Phileas policy. identifiers is required; custom dictionaries use dictionaries.", "type": "object", "additionalProperties": true } } } }, "responses": { "200": { "description": "The policy was replaced and is now active. A policy_activated audit event is recorded." }, "400": { "description": "The policy is invalid, or the request has a description or notes parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such policy, with a message. Also returned, with no body, when the owner does not exist or the caller may not reach it, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The policy was not replaced, and reason says why: policy_managed (a managed policy, which cannot be replaced) or policy_changed (it changed concurrently; reload it and retry).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/copy": { "post": { "tags": [ "Policies" ], "summary": "Copy a policy.", "description": "Creates a new policy named by name from one of the caller's policies or, for a name starting with managed_, from a built-in managed policy. The copy has the source's policy and description; a copy of a managed policy notes which one it came from, and a copy of the caller's own policy keeps its notes. The copy starts its own version history and is active at once. Admins may copy within another user's account by passing that user's username as owner. Requires the `policies:write` scope.", "operationId": "copy", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "201": { "description": "The copy's details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyDetailsResponse" } } } }, "400": { "description": "The new name is missing or invalid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such policy to copy, or the owner does not exist or the caller may not reach it.", "content": { "application/json": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "A policy with the new name already exists; reason is policy_exists.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/details": { "get": { "tags": [ "Policies" ], "summary": "Get a policy's details.", "description": "Returns a policy's name, description, notes, current revision, whether it is a built-in managed policy, and when it was created and last updated: everything except the policy itself. A name starting with managed_ returns that managed policy's details. Admins may read another user's by passing that user's username as owner. Requires the `policies:read` scope.", "operationId": "getDetails", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The policy's details.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyDetailsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such policy, or the owner does not exist or the caller may not reach it.", "content": { "application/json": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Policies" ], "summary": "Set a policy's description and notes.", "description": "Sets the description (up to 200 characters) and notes (up to 1000). A field left out is left as it is; an empty value clears it. They are not part of the policy, so this does not create a new version. Managed policies cannot be changed. Admins may change another user's by passing that user's username as owner. Requires the `policies:write` scope.", "operationId": "setDetails", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyDetailsRequest" } } } }, "responses": { "200": { "description": "The policy's details after the change.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyDetailsResponse" } } } }, "400": { "description": "The description or notes are too long.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "There is no such policy, or the owner does not exist or the caller may not reach it.", "content": { "application/json": { "schema": { "type": "object" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The policy is a managed policy; reason is policy_managed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/diff": { "get": { "tags": [ "Policy Versions" ], "summary": "Diff two revisions of a policy.", "description": "Returns an RFC 6902 JSON Patch array describing what changed between the 'from' revision and the 'to' revision. If from and to are omitted, the two most recent retained revisions are compared. Object-level changes (adds, removes, replaces) are reported per field path; array values that differ are reported as a single replace at the array path. Admins may diff another user's policy via owner. Requires the `policies:read` scope.", "operationId": "diff", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "from", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } }, { "name": "to", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Revision diff; value is omitted for removals.", "content": { "application/json": { "schema": { "type": "object", "properties": { "changes": { "type": "array", "items": { "type": "object", "properties": { "op": { "type": "string", "enum": [ "add", "remove", "replace" ] }, "path": { "type": "string" }, "value": {} } } }, "from": { "type": "integer", "format": "int32" }, "to": { "type": "integer", "format": "int32" } } } } } }, "400": { "description": "The policy name is missing, fewer than two retained revisions exist for a default diff, or only one of from/to was supplied.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The policy or a requested revision does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/rollback": { "post": { "tags": [ "Policy Versions" ], "summary": "Roll back a policy to a prior revision.", "description": "Restores the content of the specified revision as a new head revision, preserving the full history. The live policy is updated with the prior content and its revision counter is incremented. The rollback is audited. Admins may roll back another user's policy via the owner parameter. Requires the `policies:write` scope.", "operationId": "rollback", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "revision", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "201": { "description": "Rollback succeeded. Body contains the new revision number.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyRollbackResponse" } } } }, "400": { "description": "The policy name or target revision is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The policy or the target revision does not exist; the message says which. No body when the owner does not exist or the caller may not reach it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The policy was not rolled back, and reason says why: policy_managed (managed policies cannot be rolled back) or policy_changed (the policy changed concurrently; reload it and retry).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/versions": { "get": { "tags": [ "Policy Versions" ], "summary": "List retained versions of a policy.", "description": "Returns a summary of each retained snapshot for the named policy, ordered by revision descending (most recent first). Each entry carries the revision number, the timestamp the snapshot was captured, its content hash, and the username of the user whose change produced it. The history of a deleted policy is kept and listed the same way; GET /api/policies?deleted=true lists deleted policies. Admins may browse another user's history via the owner parameter. Requires the `policies:read` scope.", "operationId": "listVersions", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "Array of version summaries, most recent first.", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PolicyVersionSummary" } } } } }, "400": { "description": "The policy name is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The named policy does not exist, or a non-admin caller named another user as owner.", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PolicyVersionSummary" } } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/policies/{policyName}/versions/{revision}": { "get": { "tags": [ "Policy Versions" ], "summary": "Fetch a specific revision of a policy.", "description": "Returns the full policy JSON as it existed at the given revision. The response shape is identical to GET /api/policies/{policyName}. Admins may fetch another user's revision via the owner parameter. Requires the `policies:read` scope.", "operationId": "getVersion", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "policyName", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "revision", "in": "path", "required": true, "schema": { "type": "integer", "format": "int32" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Native policy JSON.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "400": { "description": "The policy name is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The policy or the requested revision does not exist.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/redact-lists": { "get": { "tags": [ "Always/Never Redact Lists" ], "summary": "Get the always-redact and never-redact lists.", "description": "Returns the account's always-redact and never-redact lists, and each list's revision for If-Match on PUT /api/redact-lists/{list}. Both lists are always present; an account with no saved terms returns empty arrays. Admins may read another user's lists by passing that user's username as owner. Requires the `lists:read` scope.", "operationId": "getRedactLists", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Always/Never Redact Lists" ], "summary": "Replace the always-redact and never-redact lists.", "description": "Replaces both lists in full with the supplied contents - this is not a merge. Each field is the complete desired contents of that list; a list that is omitted or sent as an empty array is cleared. Terms are trimmed and blank entries are dropped. Each list may contain up to 1000 terms, and each term may be up to 100 characters. Both lists move to their next revision. Takes no If-Match; to replace one list on the condition that it has not changed, use PUT /api/redact-lists/{list}. Admins may replace another user's lists by passing that user's username as owner. Requires the `lists:write` scope.", "operationId": "replaceRedactLists", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListsRequest" } } } }, "responses": { "200": { "description": "The lists were replaced.", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "The request body is malformed, a list has too many terms, or a term is too long.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Always/Never Redact Lists" ], "summary": "Append to the always-redact and never-redact lists.", "description": "Appends the supplied terms to the existing lists rather than replacing them - use POST to replace. Each field's terms are added to the current contents of that list; a list that is omitted or sent as an empty array is left unchanged. Terms are trimmed, blank entries are dropped, and terms already present are not added again. The resulting list may contain up to 1000 terms, and each term may be up to 100 characters; an append that would exceed the limit is rejected. Each list appended to moves to its next revision. Admins may append to another user's lists by passing that user's username as owner. Requires the `lists:write` scope.", "operationId": "appendRedactLists", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListsRequest" } } } }, "responses": { "200": { "description": "The terms were appended.", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "400": { "description": "The request body is malformed, the resulting list has too many terms, or a term is too long.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The owner does not exist, or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/redact-lists/{list}": { "get": { "tags": [ "Always/Never Redact Lists" ], "summary": "Get one redact list.", "description": "Returns one list, always or never, and its revision. The ETag header carries the revision too, quoted. Send it back in If-Match when replacing the list with PUT /api/redact-lists/{list}. A list never written is empty, at revision 0. Admins may read another user's list by passing that user's username as owner. Requires the `lists:read` scope.", "operationId": "getRedactList", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "list", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The list and its revision.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The list is not always or never, or the owner does not exist or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Always/Never Redact Lists" ], "summary": "Replace one redact list.", "description": "Replaces one list, always or never, in full with the terms in the body, leaving the other list as it is, and moves it to its next revision. Send the revision you read in If-Match, quoted as the ETag gives it, so the write is refused with 409 and reason redact_list_changed if someone changed the list since; without If-Match the list is replaced whatever its revision. Terms are trimmed and blank entries are dropped. The list may contain up to 1000 terms, and each term may be up to 100 characters. Admins may replace another user's list by passing that user's username as owner. Requires the `lists:write` scope.", "operationId": "replaceRedactList", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "If-Match", "in": "header", "required": false, "schema": { "type": "string" } }, { "name": "list", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListRequest" } } } }, "responses": { "200": { "description": "The list was replaced. The body and the ETag header give its new revision.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListResponse" } } } }, "400": { "description": "The request body is malformed, the list has too many terms, a term is too long, or If-Match is not a revision.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "The list is not always or never, or the owner does not exist or the caller may not reach it. The API does not distinguish the two, so an owner value cannot be used to discover accounts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The list is no longer at the revision given in If-Match. Nothing was written. Read the list again and retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RedactListConflictResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/reidentify": { "post": { "tags": [ "Re-identification" ], "summary": "Re-identify redacted values.", "description": "Reverses one or more values that were redacted via the CRYPTO_REPLACE or FPE_ENCRYPT_REPLACE strategy. The caller must supply a stated reason, which is recorded in the audit log alongside the actor, timestamp, and count of reversals. A user may re-identify their own values; an admin may re-identify any user's values by supplying that user's username via the owner parameter. CRYPTO_REPLACE requires policyName so the key can be resolved from the stored policy. FPE_ENCRYPT_REPLACE uses the user's per-account FPE key; if the policy specified a custom FPE configuration, also pass policyName so its key and tweak are used instead. Requires the `reidentify` scope.", "operationId": "reidentify", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReidentifyRequest" } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReidentifyResponse" } } } }, "400": { "description": "Missing or invalid request fields.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Missing or invalid API key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "Insufficient scope, disabled API, or access denied." }, "404": { "description": "Named policy not found, or owner not found.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/settings": { "get": { "tags": [ "Settings" ], "summary": "Get the admin settings.", "description": "Returns the deployment's admin settings. The Phield API key is never returned, only whether one is set. Also returns, read-only, whether cross-user access and ledger deletion are enabled and whether the signing key is externally managed, which are set by environment variables. Requires an administrator as well as the scope. Requires the `settings:read` scope.", "operationId": "getSettings", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SettingsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold settings:read, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "patch": { "tags": [ "Settings" ], "summary": "Change admin settings.", "description": "Changes the settings given in the body and leaves the rest as they are. An empty phieldApiKey removes the key. phieldUrl must be an absolute http or https URL, and is required while Phield is enabled; both are checked when a request changes Phield. Each webhookAllowlist entry must be a hostname, an IP address, or a CIDR range. Nothing is changed if any value is invalid. Returns the settings as saved, with warnings, such as a Phield API key that will be sent over http. Recorded as a settings_updated audit event naming the settings that changed and the calling API key, not their values. The read-only crossUserAccessEnabled, ledgerDeletionEnabled, and signingKeyExternallyManaged are ignored. Requires an administrator as well as the scope. Requires the `settings:write` scope.", "operationId": "updateSettings", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSettingsRequest" } } } }, "responses": { "200": { "description": "The settings as saved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SettingsResponse" } } } }, "400": { "description": "A value is invalid. The body says which, and nothing was changed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold settings:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/sign-in": { "get": { "security": [], "tags": [ "Sign-in" ], "summary": "Get the sign-in options.", "description": "Tells a sign-in page, before anyone has signed in, that password sign-in is enabled, and gives the rules a password must meet, for a page where a person sets one. Requires no API key. Returns 404 unless PASSWORD_SIGN_IN_ENABLED is true, as the other sign-in endpoints do, so a 404 means password sign-in is not available.", "operationId": "getSignInOptions", "responses": { "200": { "description": "Password sign-in is enabled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInOptionsResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "Password sign-in is not enabled." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "security": [], "tags": [ "Sign-in" ], "summary": "Sign in.", "description": "Takes a username and password. For a user enrolled in MFA, returns a single-use challenge that expires in five minutes; send it with a code to POST /api/sign-in/mfa to get the key. Otherwise returns a session key for the user, which expires after SESSION_KEY_IDLE_TIMEOUT_MINUTES without a request or SESSION_KEY_MAX_LIFETIME_MINUTES after issue. The key holds every scope, with the user's role still deciding administrator access, but cannot create API keys. A user who must change their password, or must enroll in MFA, gets a key that can only do that and sign out. A wrong password, an unknown user, a user without a password, and a deactivated user all get the same 401. Requires no API key. Returns 404 unless PASSWORD_SIGN_IN_ENABLED is true. Recorded as sign_in_succeeded or sign_in_failed with the username and client IP address, never the password.", "operationId": "signIn", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInRequest" } } } }, "responses": { "200": { "description": "Signed in, or, for a user enrolled in MFA, a challenge. A session key is returned here and nowhere else.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/SignInResponse" }, { "$ref": "#/components/schemas/SignInChallengeResponse" } ] } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The username or password is not valid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The password is right, but the user's MFA is locked after repeated bad codes. An administrator must unlock it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "Password sign-in is not enabled." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "429": { "description": "The username is locked after repeated failures (POST /api/sign-in only), or the client address is over SIGN_IN_RATE_LIMIT_PER_MINUTE. Retry-After gives the seconds to wait, and reason says which: locked or rate_limited.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInThrottledResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/sign-in/mfa": { "post": { "security": [], "tags": [ "Sign-in" ], "summary": "Complete sign-in with an MFA code.", "description": "Takes the challenge POST /api/sign-in returned and a code from the user's authenticator app, and returns the session key. The challenge is used up by any attempt, right or wrong, so a wrong code means signing in again. Each code is accepted once. The fifth consecutive bad code locks the user until an administrator unlocks them. Requires no API key. Returns 404 unless PASSWORD_SIGN_IN_ENABLED is true. Recorded as sign_in_succeeded or sign_in_failed.", "operationId": "signInMfa", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInMfaRequest" } } } }, "responses": { "200": { "description": "Signed in. The session key is returned here and nowhere else.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The challenge is unknown, used, or expired, or the code is not valid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The user's MFA is locked after repeated bad codes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "Password sign-in is not enabled." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "429": { "description": "The username is locked after repeated failures (POST /api/sign-in only), or the client address is over SIGN_IN_RATE_LIMIT_PER_MINUTE. Retry-After gives the seconds to wait, and reason says which: locked or rate_limited.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignInThrottledResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/signing-key": { "get": { "security": [], "tags": [ "Signing" ], "summary": "Get the public signing key.", "description": "Returns the operator's ES256 (ECDSA P-256) public signing key in PEM, JWK, and fingerprint form. Use the public key to verify the `X-Philter-Signature` JWT header returned on successful `POST /api/filter` and `POST /api/explain` responses when output signing is enabled. This endpoint does not require authentication.", "operationId": "getSigningKey", "responses": { "200": { "description": "Active public signing key.", "content": { "application/json": { "schema": { "type": "object", "properties": { "fingerprint": { "type": "string" }, "jwk": { "type": "object", "additionalProperties": true }, "keyId": { "type": "string" }, "pem": { "type": "string" } } } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/signing-key/regenerate": { "post": { "tags": [ "Signing" ], "summary": "Rotate the output signing key.", "description": "Generates a new ES256 keypair and makes it the active signing key. The superseded key is retained and stays retrievable through GET /api/signing-key/{keyId}, so signatures and ledger entries made with it remain verifiable; consumers should resolve each signature's key id rather than caching one key. Rotation is the response to a suspected key compromise, so it requires an administrator as well as the signing:write scope, and is recorded as a signing_key_regenerated audit event. Requires the `signing:write` scope.", "operationId": "regenerateSigningKey", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The key that is now active.", "content": { "application/json": { "schema": { "type": "object", "properties": { "keyId": { "type": "string" } } } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold signing:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The signing key is managed by PHILTER_SIGNING_KEY_PATH and cannot be rotated through the API.", "content": { "application/json": { "schema": { "type": "string" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/signing-key/{keyId}": { "get": { "security": [], "tags": [ "Signing" ], "summary": "Get a specific public signing key.", "description": "Returns a retained public signing key by its id, in PEM form. A key that has been superseded by a regeneration is kept, because redaction-ledger entries signed with it must remain verifiable. Ledger entries and exports name the key that signed them in their signingKeyId field. This endpoint does not require authentication.", "operationId": "getSigningKeyById", "parameters": [ { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Retained public signing key.", "content": { "application/json": { "schema": { "type": "object", "properties": { "active": { "type": "boolean" }, "keyId": { "type": "string" }, "pem": { "type": "string" } } } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "No key with that id is retained.", "content": { "application/json": { "schema": { "type": "string" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users": { "get": { "tags": [ "Users" ], "summary": "List users.", "description": "Lists users sorted by username, including deactivated users, with the total count. Requires an administrator as well as the scope. Requires the `users:read` scope.", "operationId": "getUsers", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "A page of users.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetUsersResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:read, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "Users" ], "summary": "Create a user.", "description": "Creates a user with a default policy and context. The role is user unless admin is given. A password is optional; without one the user can only use API keys, created with POST /api/users/{username}/api-keys. A user created with a password must change it at next sign-in, and setting it is audited as user_password_set. Requires an administrator as well as the scope. Recorded as a user_created audit event naming the calling administrator as the principal and the calling API key in the details. Requires the `users:write` scope.", "operationId": "createUser", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUserRequest" } } } }, "responses": { "201": { "description": "The user was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatedUserResponse" } } } }, "400": { "description": "The username is missing, is reserved, or is not usable in a request path (it cannot contain /, \\, ;, %, or control characters, and cannot be . or ..), the role is not user or admin, or the password is shorter than 16 characters or longer than 72 bytes in UTF-8.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "A user with that username already exists, active or deactivated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/me": { "get": { "tags": [ "Users" ], "summary": "Get the calling key's user.", "description": "Returns the user that owns the API key making the request, with whether this deployment makes MFA available and required. Does not require an administrator. Requires the `users:read` scope.", "operationId": "getCurrentUser", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The calling key's user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CurrentUserResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:read.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/me/mfa": { "post": { "tags": [ "Users" ], "summary": "Start MFA enrollment.", "description": "Generates a TOTP secret for the calling key's own user and returns it, with an otpauth:// URI to show as a QR code. The secret is returned only here and is encrypted at rest. Enrollment takes effect only once POST /api/users/me/mfa/confirm sees a valid code; starting again replaces an unconfirmed secret. Requires the mfaAvailable setting, and the scope but not an administrator. Requires the `users:write` scope.", "operationId": "startMfaEnrollment", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The secret to set up an authenticator app with.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MfaEnrollmentResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "MFA is not available on this deployment, or the user is already enrolled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/me/mfa/confirm": { "post": { "tags": [ "Users" ], "summary": "Confirm MFA enrollment.", "description": "Completes enrollment with a code from the authenticator app for the secret POST /api/users/me/mfa returned. From then on, sign-in asks for a code. Revokes the user's session keys, so the person signs in again with MFA. Requires the scope but not an administrator. Recorded as a user_mfa_enrolled audit event. Requires the `users:write` scope.", "operationId": "confirmMfaEnrollment", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MfaCodeRequest" } } } }, "responses": { "204": { "description": "MFA is enrolled." }, "400": { "description": "The code is missing or not valid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "MFA is not available, the user is already enrolled, or no enrollment was started.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/me/mfa/remove": { "post": { "tags": [ "Users" ], "summary": "Remove your own MFA enrollment.", "description": "Removes the calling key's own user's MFA, which takes a valid code so a stolen session key cannot turn MFA off. A bad code counts toward the lock. A user who has lost the device asks an administrator, who uses DELETE /api/users/{username}/mfa. Requires the scope but not an administrator. Recorded as a user_mfa_removed audit event. Requires the `users:write` scope.", "operationId": "removeOwnMfa", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MfaCodeRequest" } } } }, "responses": { "204": { "description": "MFA was removed." }, "400": { "description": "The code is missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, the code is not valid, or the user's MFA is locked.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is not enrolled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/me/password": { "put": { "tags": [ "Users" ], "summary": "Change the calling user's password.", "description": "Changes the password of the calling key's own user, which requires the current password. The new password must differ from it. Clears any required change and revokes the user's session keys, including the calling key if it is one; long-lived keys are unaffected. Requires the scope, not an administrator. Recorded as a user_password_changed audit event, without either password. Requires the `users:write` scope.", "operationId": "changeOwnPassword", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChangePasswordRequest" } } } }, "responses": { "204": { "description": "The password was changed." }, "400": { "description": "A password is missing, the new password is shorter than 16 characters or longer than 72 bytes in UTF-8, or it is the same as the current one.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the current password is not correct.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user has no password yet, so an administrator sets the first one, or the password was changed by a concurrent request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}": { "get": { "tags": [ "Users" ], "summary": "Get a user.", "description": "Returns one user by username, active or deactivated. Requires an administrator as well as the scope. Requires the `users:read` scope.", "operationId": "getUser", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The user.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:read, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/api-keys": { "get": { "tags": [ "API Keys" ], "summary": "List a user's API keys.", "description": "Lists the named user's active keys, oldest first, with the total. session=true lists only session keys and session=false only long-lived keys. Requires an administrator as well as the scope. Requires the `api-keys:read` scope.", "operationId": "getUserApiKeys", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } }, { "description": "true lists only session keys, false only long-lived keys; left out, both. total counts only the keys listed.", "name": "session", "in": "query", "required": false, "schema": { "type": "boolean" } } ], "responses": { "200": { "description": "A page of keys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetApiKeysResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:read, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "post": { "tags": [ "API Keys" ], "summary": "Create an API key for a user.", "description": "Mints a key for the named user with the scopes given in the body, and returns its value once: only the hash is stored, so the response is the one chance to capture it. The scopes must be a subset of those the calling key holds, so this cannot be used to widen access beyond the credential making the request. Requires an administrator as well as the scope. Recorded as an api_key_created audit event naming the calling user and API key. Requires the `api-keys:write` scope.", "operationId": "createApiKey", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateApiKeyRequest" } } } }, "responses": { "201": { "description": "The key was created. The value is returned here and nowhere else.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatedApiKeyResponse" } } } }, "400": { "description": "No scopes were given, or one of them is not a scope.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:write, is a session key, the caller is not an administrator, or a requested scope is not held by the calling key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no active user with that username." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/deactivate": { "post": { "tags": [ "Users" ], "summary": "Deactivate a user.", "description": "Deactivates a user: the user's API keys stop working, and the user and all of its data are retained so it can be reactivated. The last active administrator, and the calling administrator, cannot be deactivated. Requires an administrator as well as the scope. Recorded as a user_deactivated audit event naming the calling administrator as the principal and the calling API key in the details. Requires the `users:write` scope.", "operationId": "deactivateUser", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The user was deactivated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is already deactivated, is the caller, or is the last active administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/mfa": { "delete": { "tags": [ "Users" ], "summary": "Remove a user's MFA enrollment.", "description": "Removes another user's MFA, for a user who has lost their device, and clears any lock. On the caller's own user, use POST /api/users/me/mfa/remove, which takes a code. Requires an administrator as well as the scope. Recorded as a user_mfa_removed audit event naming the calling administrator and API key. Requires the `users:write` scope.", "operationId": "removeMfa", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "MFA was removed." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is the caller, or is not enrolled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/mfa/unlock": { "post": { "tags": [ "Users" ], "summary": "Unlock a user's MFA.", "description": "Unlocks a user locked after five consecutive bad MFA codes and resets the count. Requires an administrator as well as the scope. Recorded as a user_mfa_unlocked audit event naming the calling administrator and API key. Requires the `users:write` scope.", "operationId": "unlockMfa", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The user was unlocked." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is not locked.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/password": { "put": { "tags": [ "Users" ], "summary": "Set or reset a user's password.", "description": "Sets another user's password without the current one. The user must change it at next sign-in, and their session keys are revoked. On the calling administrator's own user, this sets only the first password, with no change required, which is how the admin user gets one with the bootstrap API key; after that, use PUT /api/users/me/password. Requires an administrator as well as the scope. Recorded as a user_password_reset audit event when it replaces a password, or user_password_set when the user had none, naming the calling administrator and API key, never the password. Requires the `users:write` scope.", "operationId": "setPassword", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetPasswordRequest" } } } }, "responses": { "204": { "description": "The password was set." }, "400": { "description": "The password is missing, shorter than 16 characters, or longer than 72 bytes in UTF-8.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is the caller and already has a password, or the password was changed by a concurrent request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/reactivate": { "post": { "tags": [ "Users" ], "summary": "Reactivate a user.", "description": "Reactivates a deactivated user, restoring its API keys. Requires an administrator as well as the scope. Recorded as a user_reactivated audit event naming the calling administrator as the principal and the calling API key in the details. Requires the `users:write` scope.", "operationId": "reactivateUser", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The user was reactivated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is already active.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/role": { "put": { "tags": [ "Users" ], "summary": "Set a user's role.", "description": "Sets the role to user or admin. The last active administrator cannot be demoted. Requires an administrator as well as the scope. Recorded as a user_role_changed audit event naming the calling administrator as the principal and the calling API key in the details. Requires the `users:write` scope.", "operationId": "setUserRole", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetUserRoleRequest" } } } }, "responses": { "200": { "description": "The role was set.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserResponse" } } } }, "400": { "description": "The role is missing or is not user or admin.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold users:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "The user is the last active administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/users/{username}/session-keys": { "delete": { "tags": [ "API Keys" ], "summary": "Revoke all of a user's session keys.", "description": "Revokes every session key the user holds, signing the person out everywhere. Long-lived keys are unaffected. Requires an administrator as well as the scope. Each key is recorded as an api_key_deleted audit event naming the calling administrator and API key. Requires the `api-keys:write` scope.", "operationId": "revokeUserSessionKeys", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "How many session keys were revoked.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RevokedSessionKeysResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold api-keys:write, or the caller is not an administrator.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "There is no user with that username." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/webhook": { "delete": { "tags": [ "Webhook" ], "summary": "Remove the webhook.", "description": "Removes the webhook URL and secret, so asynchronous results are no longer delivered. Admins may remove another user's webhook via the owner parameter. Recorded as a webhook_removed audit event naming the calling user and API key. Requires the `webhooks:write` scope.", "operationId": "removeWebhook", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "204": { "description": "The webhook was removed, or none was set." }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold webhooks:write.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "get": { "tags": [ "Webhook" ], "summary": "Get the webhook.", "description": "Returns the webhook URL and whether a secret is set; the secret is never returned. Admins may read another user's webhook via the owner parameter. Requires the `webhooks:read` scope.", "operationId": "getWebhook", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The webhook configuration. The url is null when none is set.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold webhooks:read.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } }, "put": { "tags": [ "Webhook" ], "summary": "Set the webhook.", "description": "Sets the webhook URL and secret. The URL must be http or https and its host must be permitted by the administrator's webhook destination allowlist, which refuses private and loopback addresses when empty. The secret must be at least 16 characters. Leave the secret out to keep the one already set, so the URL can change without rotating it. Admins may set another user's webhook via the owner parameter. Recorded as a webhook_configured audit event naming the calling user and API key; the URL and secret are not recorded. Requires the `webhooks:write` scope.", "operationId": "setWebhook", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetWebhookRequest" } } } }, "responses": { "200": { "description": "The webhook was set.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookResponse" } } } }, "400": { "description": "The URL is missing, the secret is missing and none is set yet, the URL is invalid or not permitted, or the secret is too short. The message says which.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold webhooks:write.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/webhook/deliveries": { "get": { "tags": [ "Webhook" ], "summary": "List the webhook's deliveries.", "description": "Lists the user's webhook deliveries, newest first, with each one's status, attempts, last error, and when it was queued, last changed, is next due, and was delivered, so a failing webhook can be diagnosed. The secret and the event body are not included. A delivery that ended, delivered or failed, is kept for WEBHOOK_DELIVERIES_TTL_SECONDS (30 days by default). Paged with offset and limit (25 by default, at most 100). Admins may list another user's deliveries via the owner parameter. Requires the `webhooks:read` scope.", "operationId": "getDeliveries", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 0 } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", "default": 25 } } ], "responses": { "200": { "description": "A page of deliveries and the total.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookDeliveriesResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold webhooks:read.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it." }, "406": { "description": "No representation matches Accept." }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } }, "/api/webhook/test": { "post": { "tags": [ "Webhook" ], "summary": "Send a test event to the webhook.", "description": "Sends one signed WEBHOOK_TEST event to the webhook now, signed and checked against the destination allowlist as a real delivery is, and returns what happened: whether the receiver answered 2xx, its status code, and any error. The event is not queued or retried and does not appear in GET /api/webhook/deliveries. Its body has \"test\": true so a receiver can ignore it. Waits for the receiver up to WEBHOOK_CONNECT_TIMEOUT_SECONDS and WEBHOOK_RESPONSE_TIMEOUT_SECONDS. Admins may test another user's webhook via the owner parameter. Recorded as a webhook_tested audit event with the outcome; the URL and secret are not recorded. Requires the `webhooks:write` scope.", "operationId": "testWebhook", "parameters": [ { "name": "Authorization", "in": "header", "required": true, "schema": { "type": "string" } }, { "name": "owner", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "The test was attempted. delivered says whether the receiver accepted it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookTestResponse" } } } }, "400": { "description": "Bad Request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "401": { "description": "The Authorization header is absent or the API key is not recognized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "403": { "description": "The key does not hold webhooks:write.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "404": { "description": "The owner does not exist, or the caller may not reach it." }, "406": { "description": "No representation matches Accept." }, "409": { "description": "No webhook is set, so there is nothing to test.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "413": { "description": "Content Too Large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "415": { "description": "Unsupported Media Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "500": { "description": "Internal Server Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } }, "503": { "description": "Service Unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenericResponse" } } } } } } } }, "components": { "schemas": { "ApiKeyResponse": { "type": "object", "properties": { "bootstrap": { "type": "boolean" }, "created": { "type": "string", "format": "date-time" }, "expiresAt": { "description": "Session keys only: when the key's maximum lifetime ends. Null for a long-lived key.", "type": "string", "format": "date-time" }, "id": { "type": "string" }, "idleExpiresAt": { "description": "Session keys only: when the key expires unless it is used first. Null for a long-lived key.", "type": "string", "format": "date-time" }, "lastUsedAt": { "description": "Session keys only: when the key was last used. Null for a long-lived key.", "type": "string", "format": "date-time" }, "prefix": { "type": "string" }, "scopes": { "type": "array", "items": { "type": "string" } }, "session": { "description": "Whether this is a session key, issued when a person signed in, rather than a long-lived key.", "type": "boolean" } } }, "ApiKeyScopeDescription": { "type": "object", "properties": { "description": { "description": "What the scope allows, including any requirement for an administrator.", "type": "string" }, "name": { "description": "The scope as an API key carries it, for example policies:read.", "type": "string" } } }, "ApiKeyScopesResponse": { "type": "object", "properties": { "scopes": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKeyScopeDescription" } } } }, "AuditEventView": { "properties": { "apiKeyId": { "type": "string" }, "associatedObject": { "type": "string" }, "clientIpAddress": { "type": "string" }, "details": { "type": "string" }, "event": { "type": "string" }, "requestId": { "type": "string" }, "source": { "type": "string" }, "timestamp": { "type": "string", "format": "date-time" }, "username": { "description": "The username of the user who performed the action, when the actor is a user, including a user since deactivated. null for an event with no user actor, such as one Philter recorded itself.", "type": "string" } } }, "AuditExportLimits": { "type": "object", "properties": { "defaultPageSize": { "description": "The rows a page of the export has when limit is not given.", "type": "integer", "format": "int32" }, "maxPageSize": { "description": "The most rows a page of the export can have.", "type": "integer", "format": "int32" }, "maxWindowDays": { "description": "The most days between an export's from and to dates.", "type": "integer", "format": "int32" } } }, "CallerCapabilities": { "type": "object", "properties": { "contextCount": { "description": "How many contexts the caller has, to compare with contexts.maxPerUser.", "type": "integer", "format": "int32" }, "crossUserAccess": { "description": "Whether the caller may reach other users' resources with owner and all_users: an administrator, with ADMIN_CROSS_USER_ACCESS_ENABLED on. Always false for a user who is not an administrator.", "type": "boolean" }, "ledgerDeletion": { "description": "Whether the caller may delete and purge ledger chains: an administrator, with LEDGER_DELETION_ENABLED on. Always false for a user who is not an administrator.", "type": "boolean" }, "role": { "description": "The caller's role, read when the request is made.", "type": "string" } } }, "ChangePasswordRequest": { "type": "object", "properties": { "currentPassword": { "description": "The user's current password.", "type": "string" }, "newPassword": { "description": "At least 16 characters and at most 72 bytes in UTF-8, different from the current password.", "type": "string" } } }, "ContextConflictResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the context was not created: context_exists (the caller already has a context with this name) or context_limit_reached (the caller already has the most contexts a user may have).", "type": "string", "enum": [ "context_exists", "context_limit_reached" ] } } }, "ContextEntriesExport": { "properties": { "version": { "type": "integer", "format": "int32" }, "context": { "type": "string" }, "count": { "type": "integer", "format": "int32" }, "entries": { "type": "array", "items": { "$ref": "#/components/schemas/ContextEntryExport" } } } }, "ContextEntryExport": { "properties": { "filterType": { "type": "string" }, "replacement": { "type": "string" }, "replacementUuid": { "type": "boolean" }, "tokenHash": { "type": "string" } } }, "ContextEntryView": { "properties": { "filterType": { "type": "string" }, "id": { "type": "string" }, "reads": { "type": "integer", "format": "int64" }, "replacement": { "type": "string" }, "timestamp": { "type": "string", "format": "date-time" } } }, "ContextRules": { "type": "object", "properties": { "maxEntries": { "description": "The most entries a context holds. Adding one more evicts the least-read entry. Set by MAX_CONTEXT_SIZE.", "type": "integer", "format": "int32" }, "maxPerUser": { "description": "The most contexts a user can have.", "type": "integer", "format": "int32" } } }, "CreateApiKeyRequest": { "type": "object", "properties": { "scopes": { "type": "array", "items": { "type": "string" } } } }, "CreateUserRequest": { "type": "object", "properties": { "email": { "type": "string" }, "password": { "description": "Optional. At least 16 characters and at most 72 bytes in UTF-8. The user must change it at next sign-in. Without one, the user can only use API keys.", "type": "string" }, "role": { "description": "user (the default) or admin.", "type": "string" }, "username": { "type": "string" } } }, "CreatedApiKeyResponse": { "type": "object", "properties": { "apiKey": { "type": "string" }, "id": { "type": "string" }, "scopes": { "type": "array", "items": { "type": "string" } }, "username": { "type": "string" } } }, "CreatedUserResponse": { "type": "object", "properties": { "id": { "type": "string" }, "role": { "type": "string" }, "username": { "type": "string" } } }, "CurrentUserResponse": { "type": "object", "properties": { "email": { "type": "string" }, "active": { "type": "boolean" }, "created": { "type": "string", "format": "date-time" }, "deactivatedAt": { "type": "string", "format": "date-time" }, "id": { "description": "The user's id, which audit events and other records refer to the user by.", "type": "string" }, "mfaAvailable": { "description": "Whether this deployment lets users enroll in MFA.", "type": "boolean" }, "mfaEnabled": { "description": "Whether the user is enrolled in TOTP multi-factor authentication. The secret is never returned.", "type": "boolean" }, "mfaLocked": { "description": "Whether the user's MFA is locked after repeated bad codes, until an administrator unlocks it.", "type": "boolean" }, "mfaRequired": { "description": "Whether this deployment makes every user who signs in enroll in MFA. Never true while mfaAvailable is false.", "type": "boolean" }, "passwordChangeRequired": { "description": "Whether the user must change their password at next sign-in, because an administrator set it.", "type": "boolean" }, "passwordSet": { "description": "Whether the user has a password. A user without one can only use API keys.", "type": "boolean" }, "role": { "type": "string" }, "username": { "type": "string" } } }, "CustomListConflictResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the list was not created: list_exists (the owner already has a list with this name).", "type": "string", "enum": [ "list_exists" ] } } }, "CustomListRules": { "type": "object", "properties": { "itemMaxLength": { "description": "The most characters an item can have.", "type": "integer", "format": "int32" }, "maxItems": { "description": "The most items a custom list can have.", "type": "integer", "format": "int32" } } }, "DeletedPolicySummary": { "type": "object", "properties": { "name": { "description": "The policy's name. Read its history with GET /api/policies/{policyName}/versions.", "type": "string" }, "deletedAt": { "description": "When the policy was deleted, or null for a policy deleted before Philter recorded deletions.", "type": "string", "format": "date-time" }, "deletedBy": { "description": "The username of the user who deleted it, or null when that is not known.", "type": "string" }, "latestRevision": { "description": "The most recent retained revision.", "type": "integer", "format": "int32" } } }, "GenericResponse": { "type": "object", "properties": { "message": { "type": "string" } } }, "GetAllUsersContextsResponse": { "properties": { "contexts": { "type": "array", "items": { "$ref": "#/components/schemas/OwnedNameResponse" } } } }, "GetApiKeysResponse": { "type": "object", "properties": { "apiKeys": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKeyResponse" } }, "total": { "type": "integer", "format": "int64" } } }, "GetAuditResponse": { "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/AuditEventView" } }, "total": { "type": "integer", "format": "int64" } } }, "GetContextEntriesResponse": { "properties": { "entries": { "type": "array", "items": { "$ref": "#/components/schemas/ContextEntryView" } }, "total": { "type": "integer", "format": "int32" } } }, "GetContextResponse": { "properties": { "disambiguationScope": { "type": "string", "enum": [ "document", "context" ] }, "entityTypeDisambiguation": { "type": "boolean" }, "filterTypes": { "type": "object", "additionalProperties": { "type": "integer", "format": "int64" } }, "ledger": { "type": "boolean" }, "size": { "type": "integer", "format": "int64" }, "untyped": { "type": "integer", "format": "int64" } } }, "GetContextsResponse": { "properties": { "contexts": { "type": "array", "items": { "type": "string" } } } }, "GetDocumentsResponse": { "properties": { "pendingRedactedDocuments": { "type": "array", "items": { "$ref": "#/components/schemas/PendingRedactedDocuments" } } } }, "GetLedgerResponse": { "properties": { "chains": { "type": "array", "items": { "$ref": "#/components/schemas/LedgerEntryView" } }, "total": { "type": "integer", "format": "int32" } } }, "GetListsResponse": { "type": "object", "properties": { "description": { "description": "The list's description, or an empty string if it has none.", "type": "string" }, "lists": { "type": "array", "items": { "type": "string" } } } }, "GetRedactionStatusResponse": { "properties": { "documentId": { "type": "string" }, "effectiveConfigurationHash": { "type": "string" }, "error": { "type": "string" }, "status": { "type": "string" } } }, "GetUsersResponse": { "type": "object", "properties": { "total": { "type": "integer", "format": "int64" }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/UserResponse" } } } }, "ImportContextEntriesResponse": { "properties": { "inserted": { "type": "integer", "format": "int32" }, "overwritten": { "type": "integer", "format": "int32" }, "skipped": { "type": "integer", "format": "int32" }, "total": { "type": "integer", "format": "int32" } } }, "LedgerChainResponse": { "properties": { "documentId": { "type": "string" }, "entries": { "type": "array", "items": { "$ref": "#/components/schemas/LedgerEntryView" } }, "hashChainValid": { "type": "boolean" }, "signaturesValid": { "type": "boolean" }, "signedEntries": { "type": "integer", "format": "int32" }, "unsignedEntries": { "type": "integer", "format": "int32" }, "valid": { "type": "boolean" }, "validationError": { "description": "Why the chain could not be validated. Present only then, with valid false and the hash, signature, and entry-count fields left out.", "type": "string" } } }, "LedgerEntryView": { "properties": { "type": { "type": "string" }, "documentHash": { "type": "string" }, "documentId": { "type": "string" }, "effectiveHash": { "type": "string" }, "filename": { "type": "string" }, "hash": { "type": "string" }, "policyContentHash": { "type": "string" }, "policyName": { "type": "string" }, "policyVersion": { "type": "integer", "format": "int32" }, "previousHash": { "type": "string" }, "readError": { "description": "Present only when the entry could not be read, for example because it no longer decrypts. The fields stored in the clear are still given; replacement is not.", "type": "string" }, "replacement": { "type": "string" }, "signature": { "type": "string" }, "signingKeyId": { "type": "string" }, "startPosition": { "type": "integer", "format": "int64" }, "timestamp": { "type": "string", "format": "date-time" }, "token": { "type": "string" } } }, "LedgerExport": { "properties": { "version": { "type": "integer", "format": "int32" }, "count": { "type": "integer", "format": "int32" }, "documentId": { "type": "string" }, "entries": { "type": "array", "items": { "$ref": "#/components/schemas/LedgerEntryView" } }, "signingKeys": { "type": "object", "additionalProperties": { "type": "string" } } } }, "LedgerRefusalResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the request was refused: entry_unreadable (an entry in the chain could not be read).", "type": "string", "enum": [ "entry_unreadable" ] } } }, "LegalHoldConflictResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the hold was not changed: hold_exists (the owner already has a hold with this reference) or operation_in_progress (another evidence or hold operation for the owner is active or requires recovery).", "type": "string", "enum": [ "hold_exists", "operation_in_progress" ] } } }, "LegalHoldRequest": { "type": "object", "properties": { "reason": { "type": "string" }, "reference": { "type": "string" }, "scopeType": { "type": "string" }, "scopeValue": { "description": "For document_chain, the document id (required). For user, optional: the hold covers all of its owner's evidence, and if given this must be the owner's username, which is stored either way.", "type": "string" } } }, "LegalHoldResponse": { "type": "object", "properties": { "reason": { "type": "string" }, "reference": { "type": "string" }, "scopeType": { "type": "string" }, "scopeValue": { "type": "string" }, "setAt": { "type": "string", "format": "date-time" } } }, "LegalHoldRules": { "type": "object", "properties": { "scopeTypes": { "description": "The scope types a legal hold can have.", "type": "array", "items": { "type": "string" } } } }, "LimitsResponse": { "type": "object", "properties": { "auditExport": { "description": "Audit log export limits.", "$ref": "#/components/schemas/AuditExportLimits" }, "caller": { "description": "What the caller may do.", "$ref": "#/components/schemas/CallerCapabilities" }, "contexts": { "description": "Context limits.", "$ref": "#/components/schemas/ContextRules" }, "customLists": { "description": "Custom list sizes.", "$ref": "#/components/schemas/CustomListRules" }, "legalHolds": { "description": "Legal hold rules.", "$ref": "#/components/schemas/LegalHoldRules" }, "names": { "description": "The rule for names used in a request path: custom list names, context names, legal hold references, and usernames.", "$ref": "#/components/schemas/PathSafeNameRules" }, "password": { "description": "The rules a password must meet.", "$ref": "#/components/schemas/PasswordRules" }, "policies": { "description": "Policy names, descriptions, and notes.", "$ref": "#/components/schemas/PolicyRules" }, "redactLists": { "description": "Always-redact and never-redact list sizes.", "$ref": "#/components/schemas/RedactListRules" }, "requests": { "description": "Request sizes and paging.", "$ref": "#/components/schemas/RequestLimits" }, "sessionKeys": { "description": "How long a session key, the key issued by password sign-in, stays valid.", "$ref": "#/components/schemas/SessionKeyLimits" }, "users": { "description": "User rules.", "$ref": "#/components/schemas/UserRules" }, "webhook": { "description": "Webhook rules.", "$ref": "#/components/schemas/WebhookRules" } } }, "ListSummaryResponse": { "properties": { "description": { "description": "The list's description, or an empty string if it has none.", "type": "string" }, "name": { "type": "string" }, "owner": { "description": "The username of the user the list belongs to. Present only in an all_users listing.", "type": "string" }, "size": { "description": "How many items the list holds.", "type": "integer", "format": "int32" } } }, "ManagedPolicySummary": { "type": "object", "properties": { "description": { "description": "What the managed policy redacts, or an empty string if it has no description.", "type": "string" }, "name": { "description": "The managed policy's name, beginning with managed_.", "type": "string" } } }, "MfaCodeRequest": { "type": "object", "properties": { "code": { "type": "string" } } }, "MfaEnrollmentResponse": { "type": "object", "properties": { "otpauthUri": { "description": "The otpauth:// URI to show as a QR code for an authenticator app to scan.", "type": "string" }, "secret": { "description": "The setup key, Base32, for entering by hand. Returned only here.", "type": "string" } } }, "OwnedLedgerEntryView": { "properties": { "type": { "type": "string" }, "documentHash": { "type": "string" }, "documentId": { "type": "string" }, "effectiveHash": { "type": "string" }, "filename": { "type": "string" }, "hash": { "type": "string" }, "owner": { "type": "string" }, "policyContentHash": { "type": "string" }, "policyName": { "type": "string" }, "policyVersion": { "type": "integer", "format": "int32" }, "previousHash": { "type": "string" }, "readError": { "description": "Present only when the entry could not be read, for example because it no longer decrypts. The fields stored in the clear are still given; replacement is not.", "type": "string" }, "replacement": { "type": "string" }, "signature": { "type": "string" }, "signingKeyId": { "type": "string" }, "startPosition": { "type": "integer", "format": "int64" }, "timestamp": { "type": "string", "format": "date-time" }, "token": { "type": "string" } } }, "OwnedLegalHoldResponse": { "type": "object", "properties": { "owner": { "type": "string" }, "reason": { "type": "string" }, "reference": { "type": "string" }, "scopeType": { "type": "string" }, "scopeValue": { "type": "string" }, "setAt": { "type": "string", "format": "date-time" } } }, "OwnedNameResponse": { "properties": { "name": { "type": "string" }, "owner": { "type": "string" } } }, "PasswordRules": { "type": "object", "properties": { "maxBytes": { "description": "The most bytes a password can have in UTF-8.", "type": "integer", "format": "int32" }, "minCharacters": { "description": "The fewest characters a password can have.", "type": "integer", "format": "int32" } } }, "PathSafeNameRules": { "type": "object", "properties": { "controlCharactersForbidden": { "description": "Whether control characters are refused too.", "type": "boolean" }, "forbiddenCharacters": { "description": "The characters such a name cannot contain.", "type": "array", "items": { "type": "string" } }, "reservedNames": { "description": "Whole names that are refused.", "type": "array", "items": { "type": "string" } }, "rule": { "description": "The rule in words, as Philter's error messages state it.", "type": "string" } } }, "PendingRedactedDocuments": { "properties": { "documentId": { "type": "string" }, "fileName": { "type": "string" }, "status": { "type": "string" }, "timestamp": { "type": "string", "format": "date-time" } } }, "PolicyConflictResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the policy was not changed: policy_managed (it is a built-in managed policy), policy_changed (it changed concurrently; reload it and retry), policy_exists (a policy with this name was created concurrently), or policy_default (the default policy cannot be deleted).", "type": "string", "enum": [ "policy_managed", "policy_changed", "policy_exists", "policy_default" ] } } }, "PolicyDetailsRequest": { "type": "object", "properties": { "description": { "type": "string" }, "notes": { "type": "string" } } }, "PolicyDetailsResponse": { "type": "object", "properties": { "description": { "type": "string" }, "name": { "type": "string" }, "created": { "type": "string", "format": "date-time" }, "lastUpdated": { "type": "string", "format": "date-time" }, "managed": { "type": "boolean" }, "notes": { "type": "string" }, "revision": { "type": "integer", "format": "int32" } } }, "PolicyRollbackResponse": { "type": "object", "properties": { "revision": { "type": "integer", "format": "int32" } } }, "PolicyRules": { "type": "object", "properties": { "defaultPolicyName": { "description": "The policy every user is given, which cannot be deleted.", "type": "string" }, "descriptionMaxLength": { "description": "The most characters a policy description can have.", "type": "integer", "format": "int32" }, "nameMaxLength": { "description": "The most characters a policy name can have.", "type": "integer", "format": "int32" }, "namePattern": { "description": "The regular expression a policy name must match.", "type": "string" }, "notesMaxLength": { "description": "The most characters a policy's notes can have.", "type": "integer", "format": "int32" }, "reservedNamePrefix": { "description": "The prefix of managed policies' names, which a user's policy cannot start with.", "type": "string" }, "reservedNames": { "description": "Whole names a policy cannot have, because the API's paths use them for something else.", "type": "array", "items": { "type": "string" } } } }, "PolicyVersionSummary": { "type": "object", "properties": { "author": { "description": "The username of the user whose change produced this revision, or null when it is not known: for a revision made before Philter recorded authors, for a managed policy, or for a revision first captured when the policy was used to redact.", "type": "string" }, "capturedTimestamp": { "type": "string", "format": "date-time" }, "contentHash": { "type": "string" }, "revision": { "type": "integer", "format": "int32" } } }, "RedactListConflictResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the list was not written: redact_list_changed (the list is no longer at the revision given in If-Match).", "type": "string", "enum": [ "redact_list_changed" ] } } }, "RedactListRequest": { "properties": { "terms": { "description": "The list's complete contents. An empty array or an omitted field clears the list.", "type": "array", "items": { "description": "The list's complete contents. An empty array or an omitted field clears the list.", "type": "string" } } } }, "RedactListResponse": { "type": "object", "properties": { "revision": { "description": "The list's revision, which every write of the list increments. Send it back in If-Match when replacing the list so the write is refused if someone changed the list since. A list never written is at 0. The ETag header carries the same value.", "type": "integer", "format": "int64" }, "terms": { "description": "The list's terms. A list with no saved terms is an empty array.", "type": "array", "items": { "type": "string" } } } }, "RedactListRules": { "type": "object", "properties": { "maxTerms": { "description": "The most terms each of the always-redact and never-redact lists can have.", "type": "integer", "format": "int32" }, "termMaxLength": { "description": "The most characters a term can have.", "type": "integer", "format": "int32" } } }, "RedactListsRequest": { "properties": { "alwaysRedact": { "type": "array", "items": { "type": "string" } }, "neverRedact": { "type": "array", "items": { "type": "string" } } } }, "RedactListsResponse": { "properties": { "alwaysRedact": { "type": "array", "items": { "type": "string" } }, "alwaysRedactRevision": { "description": "The always-redact list's revision, for If-Match on PUT /api/redact-lists/always.", "type": "integer", "format": "int64" }, "neverRedact": { "type": "array", "items": { "type": "string" } }, "neverRedactRevision": { "description": "The never-redact list's revision, for If-Match on PUT /api/redact-lists/never.", "type": "integer", "format": "int64" } } }, "ReidentifyRequest": { "type": "object", "properties": { "policyName": { "type": "string" }, "reason": { "type": "string" }, "strategy": { "type": "string" }, "values": { "type": "array", "items": { "type": "string" } } } }, "ReidentifyResponse": { "properties": { "results": { "type": "array", "items": { "$ref": "#/components/schemas/ReidentifyResult" } } } }, "ReidentifyResult": { "properties": { "decrypted": { "type": "string" }, "encrypted": { "type": "string" }, "error": { "type": "string" } } }, "RequestLimits": { "type": "object", "properties": { "defaultPageSize": { "description": "The page size a listing uses when limit is not given.", "type": "integer", "format": "int32" }, "maxBodyBytes": { "description": "The largest body any other POST or PUT accepts, in bytes. Set by MAX_FILE_SIZE_BYTES_OTHER.", "type": "integer", "format": "int64" }, "maxDocumentBytes": { "description": "The largest body POST /api/filter and POST /api/explain accept, in bytes. Set by MAX_FILE_SIZE_BYTES.", "type": "integer", "format": "int64" }, "maxPageSize": { "description": "The largest limit a listing honors. A larger limit is reduced to this.", "type": "integer", "format": "int32" } } }, "RevokedSessionKeysResponse": { "type": "object", "properties": { "revoked": { "type": "integer", "format": "int64" } } }, "SessionKeyLimits": { "type": "object", "properties": { "idleTimeoutMinutes": { "description": "Minutes without a request after which a session key expires. Set by SESSION_KEY_IDLE_TIMEOUT_MINUTES.", "type": "integer", "format": "int32" }, "maxLifetimeMinutes": { "description": "Minutes after issue at which a session key expires, whatever its use. Set by SESSION_KEY_MAX_LIFETIME_MINUTES.", "type": "integer", "format": "int32" } } }, "SetApiKeyScopesRequest": { "type": "object", "properties": { "scopes": { "type": "array", "items": { "type": "string" } } } }, "SetPasswordRequest": { "type": "object", "properties": { "password": { "description": "At least 16 characters and at most 72 bytes in UTF-8.", "type": "string" } } }, "SetUserRoleRequest": { "type": "object", "properties": { "role": { "description": "user or admin.", "type": "string" } } }, "SetWebhookRequest": { "type": "object", "properties": { "url": { "type": "string" }, "secret": { "type": "string" } } }, "SettingsResponse": { "type": "object", "properties": { "crossUserAccessEnabled": { "description": "Whether administrators may use owner and all_users to reach other users' resources. Set by ADMIN_CROSS_USER_ACCESS_ENABLED; read-only, and ignored by PATCH.", "type": "boolean", "readOnly": true }, "diffuseCountsEnabled": { "type": "boolean" }, "ledgerDeletionEnabled": { "description": "Whether administrators may delete and purge ledger chains. Set by LEDGER_DELETION_ENABLED; read-only, and ignored by PATCH.", "type": "boolean", "readOnly": true }, "mfaAvailable": { "type": "boolean" }, "mfaRequired": { "type": "boolean" }, "phieldApiKeySet": { "type": "boolean" }, "phieldEnabled": { "type": "boolean" }, "phieldOrganization": { "type": "string" }, "phieldSourceId": { "type": "string" }, "phieldUrl": { "type": "string" }, "signingEnabled": { "type": "boolean" }, "signingKeyExternallyManaged": { "description": "Whether the output signing key comes from PHILTER_SIGNING_KEY_PATH, in which case it cannot be rotated through the API. Read-only, and ignored by PATCH.", "type": "boolean", "readOnly": true }, "warnings": { "type": "array", "items": { "type": "string" } }, "webhookAllowlist": { "type": "string" } } }, "SignInChallengeResponse": { "type": "object", "properties": { "challenge": { "description": "Single-use. Any attempt with it, right or wrong, uses it up.", "type": "string" }, "challengeExpiresAt": { "type": "string", "format": "date-time" }, "mfaRequired": { "description": "Always true: send the challenge and a code to POST /api/sign-in/mfa.", "type": "boolean" } } }, "SignInMfaRequest": { "type": "object", "properties": { "challenge": { "type": "string" }, "code": { "type": "string" } } }, "SignInOptionsResponse": { "type": "object", "properties": { "password": { "description": "The rules a password must meet, for a page where a person sets one.", "$ref": "#/components/schemas/PasswordRules" } } }, "SignInRequest": { "type": "object", "properties": { "password": { "description": "The user's password. Never logged or audited.", "type": "string" }, "username": { "type": "string" } } }, "SignInResponse": { "type": "object", "properties": { "apiKey": { "description": "The session key. Send it as a bearer token. It is returned here and nowhere else.", "type": "string" }, "expiresAt": { "description": "When the key's maximum lifetime ends.", "type": "string", "format": "date-time" }, "id": { "description": "The session key's id, as GET /api/api-keys lists it, so a client can recognize its own key.", "type": "string" }, "idleExpiresAt": { "description": "When the key expires unless it is used first. Each request moves it forward.", "type": "string", "format": "date-time" }, "mfaEnrollmentRequired": { "description": "Whether the user must enroll in MFA first. If so, the key can only enroll with POST /api/users/me/mfa and POST /api/users/me/mfa/confirm, and sign out.", "type": "boolean" }, "passwordChangeRequired": { "description": "Whether the password must be changed first. If so, the key can only change the password with PUT /api/users/me/password and sign out.", "type": "boolean" }, "scopes": { "type": "array", "items": { "type": "string" } }, "username": { "type": "string" } } }, "SignInThrottledResponse": { "type": "object", "properties": { "message": { "type": "string" }, "reason": { "description": "Why the sign-in was refused: locked (the username is locked after repeated failures) or rate_limited (the client address is over the rate limit).", "type": "string", "enum": [ "locked", "rate_limited" ] } } }, "StatusResponse": { "type": "object", "properties": { "applicationVersion": { "type": "string" }, "gitCommit": { "type": "string" }, "redactionPolicySchemaVersion": { "type": "string" }, "status": { "type": "string" } } }, "UpdateSettingsRequest": { "type": "object", "properties": { "diffuseCountsEnabled": { "type": "boolean" }, "mfaAvailable": { "description": "Whether users may enroll in TOTP multi-factor authentication.", "type": "boolean" }, "mfaRequired": { "description": "Whether every user who signs in must enroll in MFA. Requires mfaAvailable.", "type": "boolean" }, "phieldApiKey": { "type": "string" }, "phieldEnabled": { "type": "boolean" }, "phieldOrganization": { "type": "string" }, "phieldSourceId": { "type": "string" }, "phieldUrl": { "type": "string" }, "signingEnabled": { "type": "boolean" }, "webhookAllowlist": { "type": "string" } } }, "UserResponse": { "type": "object", "properties": { "email": { "type": "string" }, "active": { "type": "boolean" }, "created": { "type": "string", "format": "date-time" }, "deactivatedAt": { "type": "string", "format": "date-time" }, "id": { "description": "The user's id, which audit events and other records refer to the user by.", "type": "string" }, "mfaEnabled": { "description": "Whether the user is enrolled in TOTP multi-factor authentication. The secret is never returned.", "type": "boolean" }, "mfaLocked": { "description": "Whether the user's MFA is locked after repeated bad codes, until an administrator unlocks it.", "type": "boolean" }, "passwordChangeRequired": { "description": "Whether the user must change their password at next sign-in, because an administrator set it.", "type": "boolean" }, "passwordSet": { "description": "Whether the user has a password. A user without one can only use API keys.", "type": "boolean" }, "role": { "type": "string" }, "username": { "type": "string" } } }, "UserRules": { "type": "object", "properties": { "reservedUsernames": { "description": "Usernames that cannot be used, compared without regard to case.", "type": "array", "items": { "type": "string" } }, "roles": { "description": "The roles a user can have.", "type": "array", "items": { "type": "string" } } } }, "WebhookDeliveriesResponse": { "type": "object", "properties": { "deliveries": { "description": "The deliveries on this page, newest first.", "type": "array", "items": { "$ref": "#/components/schemas/WebhookDeliveryView" } }, "total": { "description": "How many deliveries the user has.", "type": "integer", "format": "int64" } } }, "WebhookDeliveryView": { "type": "object", "properties": { "url": { "description": "Where the delivery is sent: the webhook URL when the event was queued.", "type": "string" }, "attempts": { "description": "How many attempts have been made.", "type": "integer", "format": "int32" }, "createdAt": { "description": "When the event was queued.", "type": "string", "format": "date-time" }, "deliveredAt": { "description": "When the receiver accepted the delivery.", "type": "string", "format": "date-time" }, "documentId": { "description": "The document the event is about.", "type": "string" }, "event": { "description": "The event: DOCUMENT_REDACTION_COMPLETE or DOCUMENT_REDACTION_FAILED.", "type": "string" }, "id": { "description": "The delivery's id, sent as X-Philter-Delivery-Id with each attempt.", "type": "string" }, "lastError": { "description": "Why the most recent attempt failed, or null.", "type": "string" }, "nextAttemptAt": { "description": "When the next attempt is due, for a delivery that is PENDING.", "type": "string", "format": "date-time" }, "status": { "description": "PENDING (waiting for its next attempt), PROCESSING (being sent), DELIVERED, or FAILED (every attempt failed).", "type": "string", "enum": [ "PENDING", "PROCESSING", "DELIVERED", "FAILED" ] }, "updatedAt": { "description": "When the delivery last changed.", "type": "string", "format": "date-time" } } }, "WebhookResponse": { "type": "object", "properties": { "url": { "type": "string" }, "secretSet": { "type": "boolean" } } }, "WebhookRules": { "type": "object", "properties": { "secretMinLength": { "description": "The fewest characters a webhook secret can have.", "type": "integer", "format": "int32" } } }, "WebhookTestResponse": { "type": "object", "properties": { "delivered": { "description": "Whether the receiver answered with a 2xx status.", "type": "boolean" }, "deliveryId": { "description": "The test event's X-Philter-Delivery-Id, to find it in the receiver's logs.", "type": "string" }, "durationMillis": { "description": "How long the attempt took, in milliseconds.", "type": "integer", "format": "int64" }, "error": { "description": "Why the test was not delivered, or null when it was.", "type": "string" }, "statusCode": { "description": "The status the receiver answered with, or null when there was no answer, such as when the connection failed, timed out, or the destination is not permitted.", "type": "integer", "format": "int32" } } } }, "securitySchemes": { "apiKey": { "description": "A Philter API key, sent as `Authorization: Bearer `. The first comes from PHILTER_BOOTSTRAP_API_KEY; create more with POST /api/api-keys.", "type": "http", "scheme": "bearer" } } }, "security": [ { "apiKey": [] } ], "tags": [ { "description": "Operations for setting, listing, retrieving, and releasing legal holds. An active hold blocks all deletion and purge of the evidence it covers until the hold is released. Every hold lifecycle event is audited.", "name": "Legal Holds" }, { "description": "Operations for browsing policy version history, diffing revisions, and rolling back to a prior revision.", "name": "Policy Versions" }, { "description": "Operations for retrieving asynchronously redacted documents.", "name": "Documents" }, { "description": "Operations for creating and managing custom lists.", "name": "Custom Lists" }, { "description": "Redact text and PDF documents.", "name": "Filter" }, { "description": "Operations for managing an account's always-redact and never-redact term lists.", "name": "Always/Never Redact Lists" }, { "description": "List, create, re-scope, and revoke API keys. A caller manages its own keys; an administrator can also manage other users' keys.", "name": "API Keys" }, { "description": "Governed, audited reversal of cryptographically-redacted values.", "name": "Re-identification" }, { "description": "Operations for creating, retrieving, deleting, and compiling redaction policies.", "name": "Policies" }, { "description": "Read-only access to the audit log.", "name": "Audit Log" }, { "description": "Redact text and return a detailed explanation of what was redacted and why.", "name": "Explain" }, { "description": "Operations for viewing, exporting, verifying, and deleting redaction-ledger chains.", "name": "Redaction Ledger" }, { "description": "The limits and rules Philter enforces, and what the caller may do.", "name": "Limits" }, { "description": "Create and manage users and their passwords. Requires an administrator, except for reading the calling key's own user and changing its own password.", "name": "Users" }, { "description": "Unauthenticated health, including the supported redaction policy schema version.", "name": "Health" }, { "description": "Read, set, and remove the user's webhook for asynchronous redaction results.", "name": "Webhook" }, { "description": "Sign in with a username and password to get a session key. Disabled by default.", "name": "Sign-in" }, { "description": "Operations for creating and managing contexts.", "name": "Contexts" }, { "description": "Read and change the deployment's admin settings. Requires an administrator.", "name": "Settings" }, { "description": "Retrieve the public signing key used to verify Philter output signatures, and rotate it.", "name": "Signing" } ] }