From 528006b4a51219854f6979fb132f88f684c6d9b6 Mon Sep 17 00:00:00 2001 From: chrisdengso <8185683+chrisdengso@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:10:03 +0000 Subject: [PATCH 1/2] feat: v0.4.0, API changes --- .speakeasy/gen.lock | 48 ++- .speakeasy/gen.yaml | 2 +- .speakeasy/openapi.json | 151 +++++++++ CHANGELOG.md | 8 + README.md | 2 + internal/cli/projects/apikeys/create.go | 105 ++++++ internal/cli/projects/apikeys/root.go | 32 ++ internal/cli/projects/root.go | 5 + internal/cli/version.go | 2 +- internal/sdk/apikeys.go | 317 ++++++++++++++++++ .../sdk/models/components/projectapikey.go | 51 +++ .../models/operations/createprojectapikey.go | 82 +++++ internal/sdk/moderationapi.go | 6 +- internal/sdk/projects.go | 3 + internal/usage/bodyschemas.go | 37 +- internal/usage/schema.go | 6 +- 16 files changed, 823 insertions(+), 34 deletions(-) create mode 100644 internal/cli/projects/apikeys/create.go create mode 100644 internal/cli/projects/apikeys/root.go create mode 100644 internal/sdk/apikeys.go create mode 100644 internal/sdk/models/components/projectapikey.go create mode 100644 internal/sdk/models/operations/createprojectapikey.go diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 1ed72d1..2fdc7b4 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,12 +1,12 @@ lockVersion: 2.0.0 id: f6fec850-ef66-45dd-9a18-cec7cc0801ce management: - docChecksum: a23b8f20e57fbf8dec16be6319a7398f + docChecksum: 1f41f7f3f1f9652138167d27f8b4518c docVersion: 1.1.0 speakeasyVersion: internal generationVersion: internal - releaseVersion: 0.3.3 - configChecksum: a95df217f2ce238441199a6ea3f715fe + releaseVersion: 0.4.0 + configChecksum: 4273d5885cbbeaedeb3ad7cdeb96d994 repoURL: https://github.com/moderation-api/cli installationURL: https://github.com/moderation-api/cli published: true @@ -96,6 +96,10 @@ trackedFiles: last_write_checksum: sha1:5db89edd5a5f6b7867090c973744f629e4498422 internal/cli/masking.go: last_write_checksum: sha1:5a69fa055d628c1b8507cd123f3cd18309aa9a53 + internal/cli/projects/apikeys/create.go: + last_write_checksum: sha1:535e92c12f0efd0833064bfb4ffb4600b0757c1b + internal/cli/projects/apikeys/root.go: + last_write_checksum: sha1:33a648830982cc6f4cfcc3a561c2848af3e336ff internal/cli/projects/create.go: last_write_checksum: sha1:2838c87019ac2cc223f135e088213eff20d185c9 internal/cli/projects/delete.go: @@ -107,7 +111,7 @@ trackedFiles: internal/cli/projects/retrieve.go: last_write_checksum: sha1:dc13d97de4946558f0675a5044cb0e243f22a4a5 internal/cli/projects/root.go: - last_write_checksum: sha1:643c0a49b24d09ea96931240c34e98daaa1b390d + last_write_checksum: sha1:4145379bf6cfa4963ef6b41beaf5c568333ce73f internal/cli/projects/update.go: last_write_checksum: sha1:86360b44e47dfff918db06784b723a60a2892fe7 internal/cli/queue/getstats.go: @@ -127,7 +131,7 @@ trackedFiles: internal/cli/root.go: last_write_checksum: sha1:d00799a3804f87479242f16bf520cb593f79ba8e internal/cli/version.go: - last_write_checksum: sha1:a77aa9a27f89f066ed45081e1f453d5f8780ac78 + last_write_checksum: sha1:a8ab87a20abe46d2c0f4928cafee3c713021c841 internal/cli/webhooks/create.go: last_write_checksum: sha1:98c73266c8954efcf0da2493ae41909df45a38f0 internal/cli/webhooks/delete.go: @@ -224,6 +228,8 @@ trackedFiles: last_write_checksum: sha1:c73d83f641b59a97479506597334a95539d34e16 internal/sdk/actions.go: last_write_checksum: sha1:d6f0d54d24673ae13bd908680018b71e7d520820 + internal/sdk/apikeys.go: + last_write_checksum: sha1:eb2143b8ecfa3044b599462841bd90d70671bf3b internal/sdk/authors.go: last_write_checksum: sha1:01eb0e9f6fa3fd9987edb54429293271203c7ec6 internal/sdk/channels.go: @@ -254,6 +260,8 @@ trackedFiles: last_write_checksum: sha1:99268e4c4433540e746d421b6a16be06e2458770 internal/sdk/models/components/project.go: last_write_checksum: sha1:be428edb928147e28c4c15c0ebdef472b6c25461 + internal/sdk/models/components/projectapikey.go: + last_write_checksum: sha1:4c561bd766b8db2042e9e6da609e3dccbfa75685 internal/sdk/models/components/projectwithkey.go: last_write_checksum: sha1:a8186b049b303d92ef01688eca3e05ea9dabef2b internal/sdk/models/components/publicauthor.go: @@ -272,6 +280,8 @@ trackedFiles: last_write_checksum: sha1:7418ba8327dcfd0855b6e1eb972bd8581a468635 internal/sdk/models/operations/createproject.go: last_write_checksum: sha1:1387c903814ffe16e3683916acff2216f912fa6a + internal/sdk/models/operations/createprojectapikey.go: + last_write_checksum: sha1:5a1c5f987689e312de108a7888b86c7353b0eec6 internal/sdk/models/operations/createwebhook.go: last_write_checksum: sha1:93c0a33438a7034b3a8f2d689b168ee254c42e79 internal/sdk/models/operations/deleteaction.go: @@ -367,13 +377,13 @@ trackedFiles: internal/sdk/models/sdkerrors/sdkdefaulterror.go: last_write_checksum: sha1:437b3a3eacb54d8648a68f80b3cd38fa5019f006 internal/sdk/moderationapi.go: - last_write_checksum: sha1:f1f147d2cf835423ff61ed5b03e4a8b2cd08a56c + last_write_checksum: sha1:dddc315a89c2907ff24d91c28784ed97fe738c5f internal/sdk/optionalnullable/optionalnullable.go: last_write_checksum: sha1:b828c94c45116e632925027e4e2c211e971e04e2 internal/sdk/optionalnullable/optionalnullable_test.go: last_write_checksum: sha1:d3991667754ed67db803aeab4a6239f6f9a1be37 internal/sdk/projects.go: - last_write_checksum: sha1:6c561630b561c50f8d0758c744f9006180e2e77b + last_write_checksum: sha1:77b021be8e1ea6b2d2aef13a536a922d0b614bb9 internal/sdk/queue.go: last_write_checksum: sha1:e7ad9e07ed4c6f302ac8b7123a7c4e8d9b4e034e internal/sdk/retry/config.go: @@ -427,9 +437,9 @@ trackedFiles: internal/testclient/testclient.go: last_write_checksum: sha1:7107e59d336235c44af152c5a61ee3896a7841fa internal/usage/bodyschemas.go: - last_write_checksum: sha1:06404c8b3b7c87ea76f8722c2aeaa1d17baac650 + last_write_checksum: sha1:f766420e385b255888deecba3146cc31d22dc3d3 internal/usage/schema.go: - last_write_checksum: sha1:7d18cd61a609d94a72ef1c29a10141425c2d2620 + last_write_checksum: sha1:04bfae1bd9a42fc9ffc14b31e09bff0d4c326b65 scripts/install.ps1: last_write_checksum: sha1:bd3a52c28f647ed881c6e8d72abf2d84216976c9 scripts/install.sh: @@ -1184,4 +1194,24 @@ examples: application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://insecure-premium.com/"} "429": application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://upset-hydrant.info/"} + createProjectApiKey: + speakeasy-default-create-project-api-key: + parameters: + path: + id: "" + requestBody: + application/json: {"name": "API key"} + responses: + "200": + application/json: {"id": "", "name": "", "projectId": "", "createdAt": "1711588645112", "secretKey": ""} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://rusty-responsibility.com"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://compassionate-chow.net"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://buzzing-convection.com/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://impractical-bin.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://damaged-bandwidth.name"} examplesVersion: 1.0.2 diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index 343023a..0c9a6f8 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -32,7 +32,7 @@ generation: generateNewTests: true skipResponseBodyAssertions: false cli: - version: 0.3.3 + version: 0.4.0 additionalDependencies: {} agentEnvironmentDetection: true arrayFlagFormat: repeatable diff --git a/.speakeasy/openapi.json b/.speakeasy/openapi.json index e418a0c..db3f0fc 100644 --- a/.speakeasy/openapi.json +++ b/.speakeasy/openapi.json @@ -10002,6 +10002,124 @@ } } }, + "/projects/{id}/api-keys": { + "post": { + "operationId": "createProjectApiKey", + "summary": "Create a project API key", + "description": "Create a new secret API key for a project and return it. Give each app or computer its own key, so you can revoke one without breaking the others. The project's existing keys keep working. Needs the `projects:write` permission.", + "tags": [ + "Projects" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The ID of the project." + }, + "required": true, + "description": "The ID of the project." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "default": "API key", + "description": "A name for the key, shown on the API keys page of the dashboard. Name it after where the key is used, for example the app or the computer.", + "type": "string", + "minLength": 1, + "maxLength": 50 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "API key created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectApiKey" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "projects.apiKeys", + "x-speakeasy-name-override": "create", + "x-speakeasy-mcp": { + "title": "Create a project API key", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + } + }, "/projects/{projectId}/channels": { "get": { "operationId": "listChannels", @@ -11314,6 +11432,39 @@ ], "additionalProperties": false }, + "ProjectApiKey": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the API key." + }, + "name": { + "type": "string", + "description": "The key's name." + }, + "projectId": { + "type": "string", + "description": "The ID of the project the key belongs to." + }, + "createdAt": { + "description": "When the key was created.", + "type": "string" + }, + "secretKey": { + "type": "string", + "description": "The new secret API key. Use it to moderate content in this project. It is also shown in the project's settings." + } + }, + "required": [ + "id", + "name", + "projectId", + "createdAt", + "secretKey" + ], + "additionalProperties": false + }, "Channel": { "type": "object", "properties": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 107fa49..60b7bd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.4.0 (2026-10-23) + +Full Changelog: [v0.3.3...v0.4.0](https://github.com/moderation-api/cli/compare/v0.3.3...v0.4.0) + +### API changes + +- `POST /projects/{id}/api-keys`: endpoint added + ## 0.3.3 (2026-10-23) Full Changelog: [v0.3.2...v0.3.3](https://github.com/moderation-api/cli/compare/v0.3.2...v0.3.3) diff --git a/README.md b/README.md index d42c1d5..0b1ca4d 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,8 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY" * [`update`](docs/modapi_projects_update.md) - Update a project * [`delete`](docs/modapi_projects_delete.md) - Delete a project * [`duplicate`](docs/modapi_projects_duplicate.md) - Duplicate a project + * [`api-keys`](docs/modapi_projects_api-keys.md) - Operations for api-keys + * [`create`](docs/modapi_projects_api-keys_create.md) - Create a project API key * [`channels`](docs/modapi_channels.md) - Operations for channels * [`list`](docs/modapi_channels_list.md) - List channels * [`create`](docs/modapi_channels_create.md) - Create a channel diff --git a/internal/cli/projects/apikeys/create.go b/internal/cli/projects/apikeys/create.go new file mode 100644 index 0000000..599c01d --- /dev/null +++ b/internal/cli/projects/apikeys/create.go @@ -0,0 +1,105 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package apikeys + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var createCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The ID of the project. [required]"}, + {FlagName: "name", Shorthand: "n", FieldPath: "Body.Name", Kind: flagutil.FlagKindString, Optional: true, HasDefault: true, DefaultStr: "API key", MinLength: 1, HasMaxLength: true, MaxLength: 50, Description: "A name for the key, shown on the API keys page of the dashboard. Name it after where the key is used, for example the app or the computer."}, +} + +// initCreateCmd initializes the create command. +func initCreateCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "create [id]", + Short: "Create a project API key", + Long: "Create a new secret API key for a project and return it. Give each app or computer its own key, so you can revoke one without breaking the others. The project's existing keys keep working. Needs the `projects:write` permission.", + Example: " modapi projects api-keys create --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runCreateCmd, + Annotations: map[string]string{ + "speakeasy_operation": "createProjectApiKey", + }, + } + flagutil.RegisterFlags(cmd, createCmdMeta) + if err := flagutil.ValidateMeta[operations.CreateProjectAPIKeyRequest](createCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for create: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, createCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for create: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The ID of the project. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "The ID of the project.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for create: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runCreateCmd executes the create command. +func runCreateCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "createProjectApiKey") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.CreateProjectAPIKeyRequest](cmd, createCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Projects.APIKeys.Create(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/projects/apikeys/root.go b/internal/cli/projects/apikeys/root.go new file mode 100644 index 0000000..7e21ade --- /dev/null +++ b/internal/cli/projects/apikeys/root.go @@ -0,0 +1,32 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package apikeys + +import ( + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +func InitApiKeysRoot(parent *cobra.Command) error { + var ApiKeysCmd = &cobra.Command{ + Use: "api-keys", + Short: "Operations for api-keys", + Long: "Operations for api-keys", + Args: cobra.NoArgs, + Annotations: map[string]string{"speakeasy_cli_group": "true"}, + RunE: func(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + return cmd.Help() + }, + Aliases: []string{"ak"}, + } + + if err := initCreateCmd(ApiKeysCmd); err != nil { + return err + } + + parent.AddCommand(ApiKeysCmd) + return nil +} diff --git a/internal/cli/projects/root.go b/internal/cli/projects/root.go index 36639fe..5aeba64 100644 --- a/internal/cli/projects/root.go +++ b/internal/cli/projects/root.go @@ -3,6 +3,7 @@ package projects import ( + "github.com/moderation-api/cli/internal/cli/projects/apikeys" "github.com/moderation-api/cli/internal/usage" "github.com/spf13/cobra" ) @@ -22,6 +23,10 @@ func InitProjectsRoot(parent *cobra.Command) error { }, } + if err := apikeys.InitApiKeysRoot(ProjectsCmd); err != nil { + return err + } + if err := initListCmd(ProjectsCmd); err != nil { return err } diff --git a/internal/cli/version.go b/internal/cli/version.go index 616339e..5d086d0 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -14,7 +14,7 @@ import ( // which propagates the value here (see cmd/modapi/main.go): // // go build -ldflags "-X main.version=x.y.z" ./cmd/modapi -var Version = "0.3.3" +var Version = "0.4.0" // BuildTime is optionally set at build time via ldflags targeting the main package. var BuildTime string diff --git a/internal/sdk/apikeys.go b/internal/sdk/apikeys.go new file mode 100644 index 0000000..6a5fe5e --- /dev/null +++ b/internal/sdk/apikeys.go @@ -0,0 +1,317 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package sdk + +import ( + "bytes" + "context" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/sdk/models/sdkerrors" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/config" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/hooks" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" + "net/http" +) + +type APIKeys struct { + rootSDK *ModerationAPI + sdkConfiguration config.SDKConfiguration + hooks *hooks.Hooks +} + +func newAPIKeys(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks *hooks.Hooks) *APIKeys { + return &APIKeys{ + rootSDK: rootSDK, + sdkConfiguration: sdkConfig, + hooks: hooks, + } +} + +// Create a project API key +// Create a new secret API key for a project and return it. Give each app or computer its own key, so you can revoke one without breaking the others. The project's existing keys keep working. Needs the `projects:write` permission. +func (s *APIKeys) Create(ctx context.Context, request operations.CreateProjectAPIKeyRequest, opts ...operations.Option) (*operations.CreateProjectAPIKeyResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/projects/{id}/api-keys", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "createProjectApiKey", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.CreateProjectAPIKeyResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out components.ProjectAPIKey + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.ProjectAPIKey = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 403: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.PermissionDeniedError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} diff --git a/internal/sdk/models/components/projectapikey.go b/internal/sdk/models/components/projectapikey.go new file mode 100644 index 0000000..9f94a3e --- /dev/null +++ b/internal/sdk/models/components/projectapikey.go @@ -0,0 +1,51 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +type ProjectAPIKey struct { + // The ID of the API key. + ID string `json:"id"` + // The key's name. + Name string `json:"name"` + // The ID of the project the key belongs to. + ProjectID string `json:"projectId"` + // When the key was created. + CreatedAt string `json:"createdAt"` + // The new secret API key. Use it to moderate content in this project. It is also shown in the project's settings. + SecretKey string `json:"secretKey"` +} + +func (p *ProjectAPIKey) GetID() string { + if p == nil { + return "" + } + return p.ID +} + +func (p *ProjectAPIKey) GetName() string { + if p == nil { + return "" + } + return p.Name +} + +func (p *ProjectAPIKey) GetProjectID() string { + if p == nil { + return "" + } + return p.ProjectID +} + +func (p *ProjectAPIKey) GetCreatedAt() string { + if p == nil { + return "" + } + return p.CreatedAt +} + +func (p *ProjectAPIKey) GetSecretKey() string { + if p == nil { + return "" + } + return p.SecretKey +} diff --git a/internal/sdk/models/operations/createprojectapikey.go b/internal/sdk/models/operations/createprojectapikey.go new file mode 100644 index 0000000..e431157 --- /dev/null +++ b/internal/sdk/models/operations/createprojectapikey.go @@ -0,0 +1,82 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type CreateProjectAPIKeyRequestBody struct { + // A name for the key, shown on the API keys page of the dashboard. Name it after where the key is used, for example the app or the computer. + Name *string `default:"API key" json:"name"` +} + +func (c CreateProjectAPIKeyRequestBody) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateProjectAPIKeyRequestBody) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateProjectAPIKeyRequestBody) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +type CreateProjectAPIKeyRequest struct { + // The ID of the project. + ID string `pathParam:"style=simple,explode=false,name=id"` + Body CreateProjectAPIKeyRequestBody `request:"mediaType=application/json"` +} + +func (c *CreateProjectAPIKeyRequest) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CreateProjectAPIKeyRequest) GetBody() CreateProjectAPIKeyRequestBody { + if c == nil { + return CreateProjectAPIKeyRequestBody{} + } + return c.Body +} + +type CreateProjectAPIKeyResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // API key created successfully + ProjectAPIKey *components.ProjectAPIKey +} + +func (c CreateProjectAPIKeyResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateProjectAPIKeyResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateProjectAPIKeyResponse) GetHTTPMeta() components.HTTPMetadata { + if c == nil { + return components.HTTPMetadata{} + } + return c.HTTPMeta +} + +func (c *CreateProjectAPIKeyResponse) GetProjectAPIKey() *components.ProjectAPIKey { + if c == nil { + return nil + } + return c.ProjectAPIKey +} diff --git a/internal/sdk/moderationapi.go b/internal/sdk/moderationapi.go index b2b0e1d..94082a5 100644 --- a/internal/sdk/moderationapi.go +++ b/internal/sdk/moderationapi.go @@ -137,10 +137,10 @@ func WithTimeout(timeout time.Duration) SDKOption { // New creates a new instance of the SDK with the provided options func New(opts ...SDKOption) *ModerationAPI { sdk := &ModerationAPI{ - SDKVersion: "0.3.3", + SDKVersion: "0.4.0", sdkConfiguration: config.SDKConfiguration{ - UserAgent: "speakeasy-sdk/go 0.3.3 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", - SDKVersion: "0.3.3", + UserAgent: "speakeasy-sdk/go 0.4.0 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", + SDKVersion: "0.4.0", GenVersion: "internal", OpenAPIDocVersion: "1.1.0", ServerList: ServerList, diff --git a/internal/sdk/projects.go b/internal/sdk/projects.go index 3bf0809..577f3d8 100644 --- a/internal/sdk/projects.go +++ b/internal/sdk/projects.go @@ -18,6 +18,8 @@ import ( ) type Projects struct { + APIKeys *APIKeys + rootSDK *ModerationAPI sdkConfiguration config.SDKConfiguration hooks *hooks.Hooks @@ -28,6 +30,7 @@ func newProjects(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hook rootSDK: rootSDK, sdkConfiguration: sdkConfig, hooks: hooks, + APIKeys: newAPIKeys(rootSDK, sdkConfig, hooks), } } diff --git a/internal/usage/bodyschemas.go b/internal/usage/bodyschemas.go index a8cfb2a..4610907 100644 --- a/internal/usage/bodyschemas.go +++ b/internal/usage/bodyschemas.go @@ -8,24 +8,25 @@ import ( ) var bodySchemas = map[string]string{ - "addWordlistWords": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"words\":{\"description\":\"Array of words to add to the wordlist. Duplicate words will be ignored.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"required\":[\"words\"],\"type\":\"object\"}", - "createAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", - "createAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_id\":{\"description\":\"External ID of the user, typically the ID of the author in your database.\",\"type\":\"string\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"required\":[\"external_id\"],\"type\":\"object\"}", - "createChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"description\":{\"description\":\"The channel's description.\",\"maxLength\":500,\"type\":\"string\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", - "createProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"brief\":{\"description\":\"What you moderate, the problems you see and your guidelines.\",\"maxLength\":2000,\"type\":\"string\"},\"context\":{\"description\":\"What the platform is and who uses it.\",\"maxLength\":2000,\"type\":\"string\"},\"domain\":{\"description\":\"The website or app the project moderates content for. Used to write the context when none is given.\",\"maxLength\":255,\"type\":\"string\"},\"name\":{\"description\":\"The project's name.\",\"maxLength\":35,\"minLength\":1,\"type\":\"string\"},\"usedByMinors\":{\"default\":false,\"description\":\"Whether children use the platform.\",\"type\":\"boolean\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", - "createWebhook": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"The webhook's description\",\"type\":[\"string\",\"null\"]},\"eventTypes\":{\"description\":\"Event types this webhook subscribes to. One webhook URL receives all events you list here.\",\"items\":{\"enum\":[\"QUEUE_ITEM_NEW\",\"QUEUE_ITEM_COMPLETED\",\"QUEUE_ITEM_ACTION\",\"QUEUE_ITEM_REJECTED\",\"QUEUE_ITEM_ALLOWED\",\"AUTHOR_BLOCKED\",\"AUTHOR_UNBLOCKED\",\"AUTHOR_SUSPENDED\",\"AUTHOR_UPDATED\",\"AUTHOR_TRUST_LEVEL_CHANGED\",\"AUTHOR_ACTION\"],\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"name\":{\"description\":\"The webhook's name, used to identify it in the dashboard\",\"minLength\":1,\"type\":\"string\"},\"url\":{\"description\":\"The webhook's URL. We'll call this URL when an event occurs.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"name\",\"url\",\"eventTypes\"],\"type\":\"object\"}", - "duplicateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"key\":{\"description\":\"Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "duplicateProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":75,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "executeAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"description\":\"Input parameters\",\"properties\":{\"actionKey\":{\"description\":\"ID or key of the action to execute\",\"type\":\"string\"},\"authorIds\":{\"description\":\"IDs of the authors to apply the action to. Provide this or contentIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"contentIds\":{\"description\":\"IDs of the content items to apply the action to. Provide this or authorIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"duration\":{\"description\":\"Optional duration in milliseconds for actions with timeouts\",\"minimum\":0,\"type\":\"number\"},\"queueId\":{\"description\":\"Optional queue ID if the action is queue-specific\",\"type\":\"string\"},\"value\":{\"description\":\"Optional value to provide with the action\",\"type\":\"string\"\x7d\x7d,\"required\":[\"actionKey\"],\"type\":\"object\"}", - "resolveQueueItem": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"comment\":{\"description\":\"Optional comment\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "submitContent": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"authorId\":{\"description\":\"The author of the content.\",\"type\":\"string\"},\"channel\":{\"description\":\"Provide a channel ID or key. Will use the project's default channel if not provided.\",\"type\":\"string\"},\"clientAction\":{\"description\":\"A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.\",\"properties\":{\"action\":{\"description\":\"Your recommendation for the content: allow, review, or reject.\",\"enum\":[\"review\",\"allow\",\"reject\"],\"type\":\"string\"},\"behavior\":{\"description\":\"How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.\",\"enum\":[\"override\",\"escalate\"],\"type\":\"string\"},\"reason\":{\"description\":\"A human-readable explanation for your recommendation.\",\"maxLength\":500,\"type\":\"string\"},\"source\":{\"description\":\"Where your recommendation came from, e.g. \\\"banned-ip\\\".\",\"maxLength\":100,\"type\":\"string\"\x7d\x7d,\"required\":[\"action\"],\"type\":\"object\"},\"content\":{\"description\":\"The content sent for moderation\",\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"},{\"description\":\"Object\",\"properties\":{\"data\":{\"additionalProperties\":{\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"}],\"title\":\"Content leaf\",\"type\":\"object\"},\"description\":\"Values in the object. Can be mixed content types.\",\"propertyNames\":{\"description\":\"The key of the content node\",\"type\":\"string\"},\"type\":\"object\"},\"type\":{\"const\":\"object\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"data\"],\"title\":\"Object\",\"type\":\"object\"}],\"type\":\"object\"},\"contentId\":{\"description\":\"The unique ID of the content in your database.\",\"maxLength\":100,\"type\":\"string\"},\"conversationId\":{\"description\":\"For example the ID of a chat room or a post\",\"type\":\"string\"},\"doNotStore\":{\"description\":\"Process the content without saving it on our servers. The item is still created and appears in the review queue, analytics and author history with its moderation results, but its content is empty, so reviewers cannot see what was submitted.\",\"type\":\"boolean\"},\"metaType\":{\"description\":\"The meta type of content being moderated\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"metadata\":{\"additionalProperties\":{},\"description\":\"Any metadata you want to store with the content\",\"propertyNames\":{\"type\":\"string\"},\"type\":\"object\"},\"policies\":{\"description\":\"(Enterprise) override the channel policies for this moderation request only.\",\"items\":{\"oneOf\":[{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"toxicity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Toxicity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"personal_information\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Personal Information\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"hate\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Hate\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_firearms\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Firearms\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"cannabis\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Cannabis\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"adult\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Adult\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"crypto\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Crypto\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_minors\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Minors\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"profanity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Profanity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"violence\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Violence\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_harm\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Harm\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"spam\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Spam\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"low_quality\",\"type\":\"string\"},\"minWords\":{\"description\":\"Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting.\",\"minimum\":1,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Low-quality content\",\"type\":\"object\"},{\"properties\":{\"comparator\":{\"description\":\"Flag images that contain \\\"at least\\\" or \\\"fewer than\\\" the configured number of faces. Defaults to at_least.\",\"enum\":[\"at_least\",\"fewer_than\"],\"type\":\"string\"},\"count\":{\"description\":\"Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face.\",\"minimum\":0,\"type\":\"integer\"},\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"face_detection\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Face detection\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"political\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Political\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"religion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Religion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"threat\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Threat\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_solicitation\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Solicitation\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"channel_circumvent\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Channel Circumvention\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illegal_trade\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illegal Trade\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"weapons\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Weapons\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"extremism\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Extremism\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"code_abuse\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Code Abuse\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"unicode_spoofing\",\"type\":\"string\"},\"signals\":{\"additionalProperties\":{\"properties\":{\"flag\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"description\":\"Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy.\",\"propertyNames\":{\"enum\":[\"mixed_script\",\"confusable_word\",\"invisible\",\"zalgo\",\"illegal\",\"encoding_damage\"],\"type\":\"string\"},\"type\":\"object\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Unicode Spoofing\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"pii\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"PII Masking\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"url\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"URL Masking\",\"type\":\"object\"},{\"properties\":{\"allowlistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"blocklistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"flag\":{\"type\":\"boolean\"},\"flagLinkShorteners\":{\"description\":\"When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win.\",\"type\":\"boolean\"},\"id\":{\"const\":\"url_risk\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"URL Risk\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"guidelineKey\":{\"type\":\"string\"},\"id\":{\"const\":\"guideline\",\"type\":\"string\"},\"instructions\":{\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\",\"instructions\",\"guidelineKey\"],\"title\":\"Guideline\",\"type\":\"object\"}],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"description\":\"Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.\",\"type\":\"number\"\x7d\x7d,\"required\":[\"content\"],\"type\":\"object\"}", - "unresolveQueueItem": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"comment\":{\"description\":\"Optional reason for unresolving the item\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "updateAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", - "updateAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"type\":\"object\"}", - "updateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"contextEnabled\":{\"description\":\"Whether earlier messages in the conversation are read when moderating a message.\",\"type\":\"boolean\"},\"description\":{\"anyOf\":[{\"maxLength\":500,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The channel's description.\"},\"enableAudioModeration\":{\"description\":\"Whether audio is moderated.\",\"type\":\"boolean\"},\"enableImageModeration\":{\"description\":\"Whether images are moderated.\",\"type\":\"boolean\"},\"enableVideoModeration\":{\"description\":\"Whether videos are moderated.\",\"type\":\"boolean\"},\"enableVoiceModeration\":{\"description\":\"Whether real-time voice streams are moderated.\",\"type\":\"boolean\"},\"flaggingMode\":{\"description\":\"`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.\",\"enum\":[\"DRY_RUN\",\"ACTIVE\"],\"type\":\"string\"},\"frameCaptureInterval\":{\"description\":\"Seconds between frames captured from a video.\",\"maximum\":60,\"minimum\":0.1,\"type\":\"number\"},\"includeTranscriptInResponse\":{\"description\":\"Whether audio and video transcripts are returned in responses.\",\"type\":\"boolean\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"maxFramesPerVideo\":{\"description\":\"The most frames moderated per video.\",\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"},\"spreadFrames\":{\"description\":\"Whether frames are spread evenly across the whole video.\",\"type\":\"boolean\"},\"transcriptionQuality\":{\"description\":\"Trade-off between transcription speed and accuracy.\",\"enum\":[\"SPEED\",\"BALANCED\",\"ACCURACY\"],\"type\":\"string\"},\"unicodeSpoofDetectionEnabled\":{\"description\":\"Whether look-alike Unicode characters are normalized before moderation.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", - "updateProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"casebookEnabled\":{\"description\":\"Whether review queue decisions are recorded in the casebook.\",\"type\":\"boolean\"},\"context\":{\"anyOf\":[{\"maxLength\":2000,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"What the platform is and who uses it.\"},\"description\":{\"anyOf\":[{\"maxLength\":500,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The project's description.\"},\"domain\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The website or app the project moderates content for. Setting it on a project without context writes one.\"},\"enableRequestLogging\":{\"description\":\"Whether moderated content is stored and shown in the dashboard.\",\"type\":\"boolean\"},\"globalFlaggingMode\":{\"description\":\"`ACTIVE` flags content. `DRY_RUN` evaluates without flagging.\",\"enum\":[\"DRY_RUN\",\"ACTIVE\"],\"type\":\"string\"},\"name\":{\"description\":\"The project's name.\",\"maxLength\":75,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "updateWebhook": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"The webhook's description\",\"type\":[\"string\",\"null\"]},\"eventTypes\":{\"description\":\"Event types this webhook subscribes to. One webhook URL receives all events you list here.\",\"items\":{\"enum\":[\"QUEUE_ITEM_NEW\",\"QUEUE_ITEM_COMPLETED\",\"QUEUE_ITEM_ACTION\",\"QUEUE_ITEM_REJECTED\",\"QUEUE_ITEM_ALLOWED\",\"AUTHOR_BLOCKED\",\"AUTHOR_UNBLOCKED\",\"AUTHOR_SUSPENDED\",\"AUTHOR_UPDATED\",\"AUTHOR_TRUST_LEVEL_CHANGED\",\"AUTHOR_ACTION\"],\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"name\":{\"description\":\"The webhook's name, used to identify it in the dashboard\",\"minLength\":1,\"type\":\"string\"},\"url\":{\"description\":\"The webhook's URL. We'll call this URL when an event occurs.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "updateWordlist": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"New description for the wordlist\",\"type\":\"string\"},\"key\":{\"description\":\"New key for the wordlist\",\"type\":\"string\"},\"name\":{\"description\":\"New name for the wordlist\",\"type\":\"string\"},\"strict\":{\"description\":\"Deprecated. Now using threshold in project settings.\",\"type\":\"boolean\"},\"words\":{\"description\":\"New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"type\":\"object\"}", + "addWordlistWords": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"words\":{\"description\":\"Array of words to add to the wordlist. Duplicate words will be ignored.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"required\":[\"words\"],\"type\":\"object\"}", + "createAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", + "createAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_id\":{\"description\":\"External ID of the user, typically the ID of the author in your database.\",\"type\":\"string\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"required\":[\"external_id\"],\"type\":\"object\"}", + "createChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"description\":{\"description\":\"The channel's description.\",\"maxLength\":500,\"type\":\"string\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", + "createProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"brief\":{\"description\":\"What you moderate, the problems you see and your guidelines.\",\"maxLength\":2000,\"type\":\"string\"},\"context\":{\"description\":\"What the platform is and who uses it.\",\"maxLength\":2000,\"type\":\"string\"},\"domain\":{\"description\":\"The website or app the project moderates content for. Used to write the context when none is given.\",\"maxLength\":255,\"type\":\"string\"},\"name\":{\"description\":\"The project's name.\",\"maxLength\":35,\"minLength\":1,\"type\":\"string\"},\"usedByMinors\":{\"default\":false,\"description\":\"Whether children use the platform.\",\"type\":\"boolean\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", + "createProjectApiKey": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"name\":{\"default\":\"API key\",\"description\":\"A name for the key, shown on the API keys page of the dashboard. Name it after where the key is used, for example the app or the computer.\",\"maxLength\":50,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "createWebhook": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"The webhook's description\",\"type\":[\"string\",\"null\"]},\"eventTypes\":{\"description\":\"Event types this webhook subscribes to. One webhook URL receives all events you list here.\",\"items\":{\"enum\":[\"QUEUE_ITEM_NEW\",\"QUEUE_ITEM_COMPLETED\",\"QUEUE_ITEM_ACTION\",\"QUEUE_ITEM_REJECTED\",\"QUEUE_ITEM_ALLOWED\",\"AUTHOR_BLOCKED\",\"AUTHOR_UNBLOCKED\",\"AUTHOR_SUSPENDED\",\"AUTHOR_UPDATED\",\"AUTHOR_TRUST_LEVEL_CHANGED\",\"AUTHOR_ACTION\"],\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"name\":{\"description\":\"The webhook's name, used to identify it in the dashboard\",\"minLength\":1,\"type\":\"string\"},\"url\":{\"description\":\"The webhook's URL. We'll call this URL when an event occurs.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"name\",\"url\",\"eventTypes\"],\"type\":\"object\"}", + "duplicateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"key\":{\"description\":\"Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "duplicateProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":75,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "executeAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"description\":\"Input parameters\",\"properties\":{\"actionKey\":{\"description\":\"ID or key of the action to execute\",\"type\":\"string\"},\"authorIds\":{\"description\":\"IDs of the authors to apply the action to. Provide this or contentIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"contentIds\":{\"description\":\"IDs of the content items to apply the action to. Provide this or authorIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"duration\":{\"description\":\"Optional duration in milliseconds for actions with timeouts\",\"minimum\":0,\"type\":\"number\"},\"queueId\":{\"description\":\"Optional queue ID if the action is queue-specific\",\"type\":\"string\"},\"value\":{\"description\":\"Optional value to provide with the action\",\"type\":\"string\"\x7d\x7d,\"required\":[\"actionKey\"],\"type\":\"object\"}", + "resolveQueueItem": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"comment\":{\"description\":\"Optional comment\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "submitContent": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"authorId\":{\"description\":\"The author of the content.\",\"type\":\"string\"},\"channel\":{\"description\":\"Provide a channel ID or key. Will use the project's default channel if not provided.\",\"type\":\"string\"},\"clientAction\":{\"description\":\"A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.\",\"properties\":{\"action\":{\"description\":\"Your recommendation for the content: allow, review, or reject.\",\"enum\":[\"review\",\"allow\",\"reject\"],\"type\":\"string\"},\"behavior\":{\"description\":\"How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.\",\"enum\":[\"override\",\"escalate\"],\"type\":\"string\"},\"reason\":{\"description\":\"A human-readable explanation for your recommendation.\",\"maxLength\":500,\"type\":\"string\"},\"source\":{\"description\":\"Where your recommendation came from, e.g. \\\"banned-ip\\\".\",\"maxLength\":100,\"type\":\"string\"\x7d\x7d,\"required\":[\"action\"],\"type\":\"object\"},\"content\":{\"description\":\"The content sent for moderation\",\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"},{\"description\":\"Object\",\"properties\":{\"data\":{\"additionalProperties\":{\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"}],\"title\":\"Content leaf\",\"type\":\"object\"},\"description\":\"Values in the object. Can be mixed content types.\",\"propertyNames\":{\"description\":\"The key of the content node\",\"type\":\"string\"},\"type\":\"object\"},\"type\":{\"const\":\"object\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"data\"],\"title\":\"Object\",\"type\":\"object\"}],\"type\":\"object\"},\"contentId\":{\"description\":\"The unique ID of the content in your database.\",\"maxLength\":100,\"type\":\"string\"},\"conversationId\":{\"description\":\"For example the ID of a chat room or a post\",\"type\":\"string\"},\"doNotStore\":{\"description\":\"Process the content without saving it on our servers. The item is still created and appears in the review queue, analytics and author history with its moderation results, but its content is empty, so reviewers cannot see what was submitted.\",\"type\":\"boolean\"},\"metaType\":{\"description\":\"The meta type of content being moderated\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"metadata\":{\"additionalProperties\":{},\"description\":\"Any metadata you want to store with the content\",\"propertyNames\":{\"type\":\"string\"},\"type\":\"object\"},\"policies\":{\"description\":\"(Enterprise) override the channel policies for this moderation request only.\",\"items\":{\"oneOf\":[{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"toxicity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Toxicity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"personal_information\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Personal Information\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"hate\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Hate\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_firearms\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Firearms\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"cannabis\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Cannabis\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"adult\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Adult\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"crypto\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Crypto\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_minors\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Minors\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"profanity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Profanity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"violence\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Violence\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_harm\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Harm\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"spam\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Spam\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"low_quality\",\"type\":\"string\"},\"minWords\":{\"description\":\"Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting.\",\"minimum\":1,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Low-quality content\",\"type\":\"object\"},{\"properties\":{\"comparator\":{\"description\":\"Flag images that contain \\\"at least\\\" or \\\"fewer than\\\" the configured number of faces. Defaults to at_least.\",\"enum\":[\"at_least\",\"fewer_than\"],\"type\":\"string\"},\"count\":{\"description\":\"Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face.\",\"minimum\":0,\"type\":\"integer\"},\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"face_detection\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Face detection\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"political\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Political\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"religion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Religion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"threat\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Threat\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_solicitation\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Solicitation\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"channel_circumvent\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Channel Circumvention\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illegal_trade\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illegal Trade\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"weapons\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Weapons\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"extremism\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Extremism\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"code_abuse\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Code Abuse\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"unicode_spoofing\",\"type\":\"string\"},\"signals\":{\"additionalProperties\":{\"properties\":{\"flag\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"description\":\"Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy.\",\"propertyNames\":{\"enum\":[\"mixed_script\",\"confusable_word\",\"invisible\",\"zalgo\",\"illegal\",\"encoding_damage\"],\"type\":\"string\"},\"type\":\"object\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Unicode Spoofing\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"pii\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"PII Masking\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"url\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"URL Masking\",\"type\":\"object\"},{\"properties\":{\"allowlistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"blocklistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"flag\":{\"type\":\"boolean\"},\"flagLinkShorteners\":{\"description\":\"When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win.\",\"type\":\"boolean\"},\"id\":{\"const\":\"url_risk\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"URL Risk\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"guidelineKey\":{\"type\":\"string\"},\"id\":{\"const\":\"guideline\",\"type\":\"string\"},\"instructions\":{\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\",\"instructions\",\"guidelineKey\"],\"title\":\"Guideline\",\"type\":\"object\"}],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"description\":\"Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.\",\"type\":\"number\"\x7d\x7d,\"required\":[\"content\"],\"type\":\"object\"}", + "unresolveQueueItem": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"comment\":{\"description\":\"Optional reason for unresolving the item\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "updateAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", + "updateAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"type\":\"object\"}", + "updateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"contextEnabled\":{\"description\":\"Whether earlier messages in the conversation are read when moderating a message.\",\"type\":\"boolean\"},\"description\":{\"anyOf\":[{\"maxLength\":500,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The channel's description.\"},\"enableAudioModeration\":{\"description\":\"Whether audio is moderated.\",\"type\":\"boolean\"},\"enableImageModeration\":{\"description\":\"Whether images are moderated.\",\"type\":\"boolean\"},\"enableVideoModeration\":{\"description\":\"Whether videos are moderated.\",\"type\":\"boolean\"},\"enableVoiceModeration\":{\"description\":\"Whether real-time voice streams are moderated.\",\"type\":\"boolean\"},\"flaggingMode\":{\"description\":\"`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.\",\"enum\":[\"DRY_RUN\",\"ACTIVE\"],\"type\":\"string\"},\"frameCaptureInterval\":{\"description\":\"Seconds between frames captured from a video.\",\"maximum\":60,\"minimum\":0.1,\"type\":\"number\"},\"includeTranscriptInResponse\":{\"description\":\"Whether audio and video transcripts are returned in responses.\",\"type\":\"boolean\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"maxFramesPerVideo\":{\"description\":\"The most frames moderated per video.\",\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"},\"spreadFrames\":{\"description\":\"Whether frames are spread evenly across the whole video.\",\"type\":\"boolean\"},\"transcriptionQuality\":{\"description\":\"Trade-off between transcription speed and accuracy.\",\"enum\":[\"SPEED\",\"BALANCED\",\"ACCURACY\"],\"type\":\"string\"},\"unicodeSpoofDetectionEnabled\":{\"description\":\"Whether look-alike Unicode characters are normalized before moderation.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", + "updateProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"casebookEnabled\":{\"description\":\"Whether review queue decisions are recorded in the casebook.\",\"type\":\"boolean\"},\"context\":{\"anyOf\":[{\"maxLength\":2000,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"What the platform is and who uses it.\"},\"description\":{\"anyOf\":[{\"maxLength\":500,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The project's description.\"},\"domain\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The website or app the project moderates content for. Setting it on a project without context writes one.\"},\"enableRequestLogging\":{\"description\":\"Whether moderated content is stored and shown in the dashboard.\",\"type\":\"boolean\"},\"globalFlaggingMode\":{\"description\":\"`ACTIVE` flags content. `DRY_RUN` evaluates without flagging.\",\"enum\":[\"DRY_RUN\",\"ACTIVE\"],\"type\":\"string\"},\"name\":{\"description\":\"The project's name.\",\"maxLength\":75,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "updateWebhook": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"The webhook's description\",\"type\":[\"string\",\"null\"]},\"eventTypes\":{\"description\":\"Event types this webhook subscribes to. One webhook URL receives all events you list here.\",\"items\":{\"enum\":[\"QUEUE_ITEM_NEW\",\"QUEUE_ITEM_COMPLETED\",\"QUEUE_ITEM_ACTION\",\"QUEUE_ITEM_REJECTED\",\"QUEUE_ITEM_ALLOWED\",\"AUTHOR_BLOCKED\",\"AUTHOR_UNBLOCKED\",\"AUTHOR_SUSPENDED\",\"AUTHOR_UPDATED\",\"AUTHOR_TRUST_LEVEL_CHANGED\",\"AUTHOR_ACTION\"],\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"name\":{\"description\":\"The webhook's name, used to identify it in the dashboard\",\"minLength\":1,\"type\":\"string\"},\"url\":{\"description\":\"The webhook's URL. We'll call this URL when an event occurs.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "updateWordlist": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"New description for the wordlist\",\"type\":\"string\"},\"key\":{\"description\":\"New key for the wordlist\",\"type\":\"string\"},\"name\":{\"description\":\"New name for the wordlist\",\"type\":\"string\"},\"strict\":{\"description\":\"Deprecated. Now using threshold in project settings.\",\"type\":\"boolean\"},\"words\":{\"description\":\"New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"type\":\"object\"}", } func EmitBodySchema(w io.Writer, operationID string) error { diff --git a/internal/usage/schema.go b/internal/usage/schema.go index 97f53f8..644fdcc 100644 --- a/internal/usage/schema.go +++ b/internal/usage/schema.go @@ -28,7 +28,7 @@ type usageNode struct { } var usageNodes = map[string]usageNode{ - "": {head: "name \"modapi\"\nbin \"modapi\"\nabout \"Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks.\"\nversion \"0.3.3\"\nconfig {\n file \"~/.config/modapi/config.yaml\"\n}", flags: []usageItem{{key: "usage", text: "flag \"--usage\" help=\"Print the CLI Usage schema in KDL format\" global=#true"}, {key: "version", text: "flag \"-v --version\" help=\"Print the CLI version\""}, {key: "output-format", text: "flag \"-o --output-format \" help=\"Specify the output format. Options: pretty, json, yaml, table, toon.\" global=#true config=\"output_format\" default=\"pretty\""}, {key: "color", text: "flag \"--color \" help=\"Control colored output: auto (color when output is a TTY), always, or never. Respects NO_COLOR and FORCE_COLOR env vars.\" global=#true default=\"auto\""}, {key: "jq", text: "flag \"-q --jq \" help=\"Filter and transform output using a jq expression (e.g., '.name', '.items[] | .id')\" global=#true"}, {key: "raw-output", text: "flag \"--raw-output\" help=\"Write --jq string results as raw text instead of JSON strings (like jq -r); non-string results stay JSON\" global=#true default=#false"}, {key: "server-url", text: "flag \"--server-url \" help=\"Override the default server URL\" global=#true"}, {key: "server", text: "flag \"--server \" help=\"Select a server by index (for indexed servers) or name (for named servers)\" global=#true"}, {key: "header", text: "flag \"-H --header \" help=\"Set a custom HTTP request header (format: \\\"Key: Value\\\"). Can be specified multiple times.\" global=#true var=#true"}, {key: "include-headers", text: "flag \"--include-headers\" help=\"Include HTTP response headers in the output\" global=#true default=#false"}, {key: "timeout", text: "flag \"--timeout \" help=\"HTTP request timeout (e.g., 30s, 5m, 100ms)\" global=#true config=\"timeout\""}, {key: "interactive", text: "flag \"--interactive\" help=\"Prompt for missing inputs and open guided configure/auth forms (forms fall back to line prompts on stdin off-TTY)\" global=#true default=#true"}, {key: "no-interactive", text: "flag \"--no-interactive\" help=\"Disable all interactive features (auto-prompting, explorer auto-launch, TUI forms)\" global=#true default=#false"}, {key: "dry-run", text: "flag \"--dry-run\" help=\"Preview API requests without sending them (no network, no OS keychain). Human preview on stderr; with -o json or --jq, one JSON object per request on stdout. Local mutation commands (auth login, auth logout and configure) make no request: they skip prompts and writes and report a no-op (stderr, or one JSON object on stdout in the machine form)\" global=#true default=#false"}, {key: "debug", text: "flag \"-d --debug\" help=\"Log request and response diagnostics to stderr\" global=#true default=#false"}, {key: "agent-mode", text: "flag \"--agent-mode\" help=\"Enable structured errors and default TOON output for AI coding agents. Automatically enabled when a known agent environment is detected (CLAUDECODE, CURSOR_AGENT, etc.). Use --agent-mode=false to disable.\" global=#true default=#false"}, {key: "secret-key", text: "flag \"--secret-key \" help=\"HTTP Bearer\" global=#true env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\""}, {key: "no-keyring", text: "flag \"--no-keyring\" help=\"Never read or write the OS keychain; store secrets in the config file instead\" global=#true env=\"MODAPI_NO_KEYRING\" config=\"no_keyring\" default=#false"}}, children: []string{"account", "actions", "auth", "authors", "channels", "completion", "configure", "content", "explore", "help", "projects", "queue", "version", "webhook-secret", "webhooks", "whoami", "wordlist"}}, + "": {head: "name \"modapi\"\nbin \"modapi\"\nabout \"Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks.\"\nversion \"0.4.0\"\nconfig {\n file \"~/.config/modapi/config.yaml\"\n}", flags: []usageItem{{key: "usage", text: "flag \"--usage\" help=\"Print the CLI Usage schema in KDL format\" global=#true"}, {key: "version", text: "flag \"-v --version\" help=\"Print the CLI version\""}, {key: "output-format", text: "flag \"-o --output-format \" help=\"Specify the output format. Options: pretty, json, yaml, table, toon.\" global=#true config=\"output_format\" default=\"pretty\""}, {key: "color", text: "flag \"--color \" help=\"Control colored output: auto (color when output is a TTY), always, or never. Respects NO_COLOR and FORCE_COLOR env vars.\" global=#true default=\"auto\""}, {key: "jq", text: "flag \"-q --jq \" help=\"Filter and transform output using a jq expression (e.g., '.name', '.items[] | .id')\" global=#true"}, {key: "raw-output", text: "flag \"--raw-output\" help=\"Write --jq string results as raw text instead of JSON strings (like jq -r); non-string results stay JSON\" global=#true default=#false"}, {key: "server-url", text: "flag \"--server-url \" help=\"Override the default server URL\" global=#true"}, {key: "server", text: "flag \"--server \" help=\"Select a server by index (for indexed servers) or name (for named servers)\" global=#true"}, {key: "header", text: "flag \"-H --header \" help=\"Set a custom HTTP request header (format: \\\"Key: Value\\\"). Can be specified multiple times.\" global=#true var=#true"}, {key: "include-headers", text: "flag \"--include-headers\" help=\"Include HTTP response headers in the output\" global=#true default=#false"}, {key: "timeout", text: "flag \"--timeout \" help=\"HTTP request timeout (e.g., 30s, 5m, 100ms)\" global=#true config=\"timeout\""}, {key: "interactive", text: "flag \"--interactive\" help=\"Prompt for missing inputs and open guided configure/auth forms (forms fall back to line prompts on stdin off-TTY)\" global=#true default=#true"}, {key: "no-interactive", text: "flag \"--no-interactive\" help=\"Disable all interactive features (auto-prompting, explorer auto-launch, TUI forms)\" global=#true default=#false"}, {key: "dry-run", text: "flag \"--dry-run\" help=\"Preview API requests without sending them (no network, no OS keychain). Human preview on stderr; with -o json or --jq, one JSON object per request on stdout. Local mutation commands (auth login, auth logout and configure) make no request: they skip prompts and writes and report a no-op (stderr, or one JSON object on stdout in the machine form)\" global=#true default=#false"}, {key: "debug", text: "flag \"-d --debug\" help=\"Log request and response diagnostics to stderr\" global=#true default=#false"}, {key: "agent-mode", text: "flag \"--agent-mode\" help=\"Enable structured errors and default TOON output for AI coding agents. Automatically enabled when a known agent environment is detected (CLAUDECODE, CURSOR_AGENT, etc.). Use --agent-mode=false to disable.\" global=#true default=#false"}, {key: "secret-key", text: "flag \"--secret-key \" help=\"HTTP Bearer\" global=#true env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\""}, {key: "no-keyring", text: "flag \"--no-keyring\" help=\"Never read or write the OS keychain; store secrets in the config file instead\" global=#true env=\"MODAPI_NO_KEYRING\" config=\"no_keyring\" default=#false"}}, children: []string{"account", "actions", "auth", "authors", "channels", "completion", "configure", "content", "explore", "help", "projects", "queue", "version", "webhook-secret", "webhooks", "whoami", "wordlist"}}, "account": {head: "cmd \"account\" help=\"Operations for account\"", children: []string{"retrieve"}}, "account retrieve": {head: "cmd \"retrieve\" help=\"Get account details\"", flags: []usageItem{{key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, "actions": {head: "cmd \"actions\" help=\"Operations for actions\"", children: []string{"create", "delete", "execute", "list", "retrieve", "update"}}, @@ -65,7 +65,9 @@ var usageNodes = map[string]usageNode{ "content submit": {head: "cmd \"submit\" help=\"Submit content\"", flags: []usageItem{{key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "content", text: " flag \"--content \" help=\"{ \\\"text\\\": string } | { \\\"url\\\": string, \\\"data\\\": string } | { \\\"url\\\": string } | { \\\"data\\\": object }\""}, {key: "content.text", text: " flag \"--content.text \" help=\"Text_request variant as JSON\""}, {key: "content.text.text", text: " flag \"--content.text.text \" help=\"The content text [required]\""}, {key: "content.image", text: " flag \"--content.image \" help=\"Image_request variant as JSON\""}, {key: "content.image.url", text: " flag \"--content.image.url \" help=\"A public URL of the image content. Either url or data must be provided.\""}, {key: "content.image.data", text: " flag \"--content.image.data \" help=\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\""}, {key: "content.video", text: " flag \"--content.video