From add1ad208d6ca89b0a21b889396449fd0ef7fb52 Mon Sep 17 00:00:00 2001 From: chrisdengso <8185683+chrisdengso@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:29:15 +0000 Subject: [PATCH] feat: v0.5.0, API changes --- .speakeasy/gen.lock | 317 +- .speakeasy/gen.yaml | 2 +- .speakeasy/openapi.json | 11255 +++++++++------- CHANGELOG.md | 99 + README.md | 18 +- internal/cli/analytics/authors.go | 84 + internal/cli/analytics/automation.go | 83 + internal/cli/analytics/channels.go | 82 + internal/cli/analytics/policies.go | 84 + internal/cli/analytics/root.go | 51 + internal/cli/analytics/summary.go | 83 + internal/cli/analytics/volumes.go | 83 + internal/cli/authors/list.go | 5 +- internal/cli/feedback/create.go | 96 + internal/cli/feedback/root.go | 31 + internal/cli/queue/getstats.go | 8 +- internal/cli/queue/items/list.go | 19 +- internal/cli/queue/items/resolve.go | 20 +- internal/cli/queue/items/resolvemany.go | 107 + internal/cli/queue/items/root.go | 4 + internal/cli/queue/items/unresolve.go | 20 +- internal/cli/queue/list.go | 107 + internal/cli/queue/retrieve.go | 8 +- internal/cli/queue/root.go | 4 + internal/cli/root.go | 8 + internal/cli/version.go | 2 +- internal/cli/wordlist/create.go | 96 + internal/cli/wordlist/root.go | 4 + internal/cli/wordlist/update.go | 4 +- internal/sdk/analytics.go | 1897 +++ internal/sdk/authors.go | 2 +- internal/sdk/feedback.go | 270 + internal/sdk/items.go | 303 +- .../sdk/models/components/notfounderror.go | 20 +- .../sdk/models/components/publicauthor.go | 22 +- internal/sdk/models/components/queue.go | 42 + internal/sdk/models/components/queueitem.go | 1078 ++ .../sdk/models/components/queuewithcounts.go | 69 + .../sdk/models/operations/createfeedback.go | 205 + .../sdk/models/operations/createwordlist.go | 174 + .../models/operations/getanalyticsauthors.go | 208 + .../operations/getanalyticsautomation.go | 289 + .../models/operations/getanalyticschannels.go | 201 + .../models/operations/getanalyticspolicies.go | 208 + .../models/operations/getanalyticssummary.go | 406 + .../models/operations/getanalyticsvolumes.go | 250 + internal/sdk/models/operations/getqueue.go | 557 +- .../sdk/models/operations/getqueuestats.go | 201 +- internal/sdk/models/operations/listauthors.go | 35 +- .../sdk/models/operations/listqueueitems.go | 322 +- internal/sdk/models/operations/listqueues.go | 102 + .../sdk/models/operations/resolvequeueitem.go | 37 +- .../models/operations/resolvequeueitems.go | 102 + .../sdk/models/operations/submitcontent.go | 162 +- .../models/operations/unresolvequeueitem.go | 53 +- .../sdk/models/operations/updatewordlist.go | 2 +- .../sdk/models/sdkerrors/notfounderror.go | 1 + internal/sdk/moderationapi.go | 10 +- internal/sdk/queue.go | 382 +- internal/sdk/wordlist.go | 241 +- internal/usage/bodyschemas.go | 7 +- internal/usage/schema.go | 34 +- 62 files changed, 14745 insertions(+), 5931 deletions(-) create mode 100644 internal/cli/analytics/authors.go create mode 100644 internal/cli/analytics/automation.go create mode 100644 internal/cli/analytics/channels.go create mode 100644 internal/cli/analytics/policies.go create mode 100644 internal/cli/analytics/root.go create mode 100644 internal/cli/analytics/summary.go create mode 100644 internal/cli/analytics/volumes.go create mode 100644 internal/cli/feedback/create.go create mode 100644 internal/cli/feedback/root.go create mode 100644 internal/cli/queue/items/resolvemany.go create mode 100644 internal/cli/queue/list.go create mode 100644 internal/cli/wordlist/create.go create mode 100644 internal/sdk/analytics.go create mode 100644 internal/sdk/feedback.go create mode 100644 internal/sdk/models/components/queue.go create mode 100644 internal/sdk/models/components/queueitem.go create mode 100644 internal/sdk/models/components/queuewithcounts.go create mode 100644 internal/sdk/models/operations/createfeedback.go create mode 100644 internal/sdk/models/operations/createwordlist.go create mode 100644 internal/sdk/models/operations/getanalyticsauthors.go create mode 100644 internal/sdk/models/operations/getanalyticsautomation.go create mode 100644 internal/sdk/models/operations/getanalyticschannels.go create mode 100644 internal/sdk/models/operations/getanalyticspolicies.go create mode 100644 internal/sdk/models/operations/getanalyticssummary.go create mode 100644 internal/sdk/models/operations/getanalyticsvolumes.go create mode 100644 internal/sdk/models/operations/listqueues.go create mode 100644 internal/sdk/models/operations/resolvequeueitems.go diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 2fdc7b4..6b1507d 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: 1f41f7f3f1f9652138167d27f8b4518c + docChecksum: ed2fd953cba3e99fb81365252925a0d4 docVersion: 1.1.0 speakeasyVersion: internal generationVersion: internal - releaseVersion: 0.4.0 - configChecksum: 4273d5885cbbeaedeb3ad7cdeb96d994 + releaseVersion: 0.5.0 + configChecksum: 02a247def0b187defcac9b37ddb44f1f repoURL: https://github.com/moderation-api/cli installationURL: https://github.com/moderation-api/cli published: true @@ -60,6 +60,20 @@ trackedFiles: last_write_checksum: sha1:bfc573f85ebfb3ddd27c0168b84918d3bed46899 internal/cli/actions/update.go: last_write_checksum: sha1:0636aeef587e5fa1bdf536a7a7cac8077b2e3f4d + internal/cli/analytics/authors.go: + last_write_checksum: sha1:dd0a5a802a85800cf68c373b7c2c7f4b1df005d6 + internal/cli/analytics/automation.go: + last_write_checksum: sha1:a946f50349d48e9b89b122c87ab178eeb685cf3a + internal/cli/analytics/channels.go: + last_write_checksum: sha1:cfc6e27421fe289db1e86c794ee81117c111908d + internal/cli/analytics/policies.go: + last_write_checksum: sha1:f582c025947498f389390db63e2aa3896b94d1d8 + internal/cli/analytics/root.go: + last_write_checksum: sha1:cbd4f3fa6b8af32df042ada019d28163d454700d + internal/cli/analytics/summary.go: + last_write_checksum: sha1:8213ca387a65ad22862c3716eadfa97e628e5369 + internal/cli/analytics/volumes.go: + last_write_checksum: sha1:324996d8d12662d1d801978a342779b4b8ed4572 internal/cli/auth.go: last_write_checksum: sha1:b4a5f6a9339e2f0c230ecdab7a4da8a907feb0d8 internal/cli/authors/create.go: @@ -67,7 +81,7 @@ trackedFiles: internal/cli/authors/delete.go: last_write_checksum: sha1:17f39ef321de2cf976c832ef28a0ca544bf3fed6 internal/cli/authors/list.go: - last_write_checksum: sha1:35df9eee07370d49ecd6da3c58bd7f10bba6ab18 + last_write_checksum: sha1:63bfd2e95ced5c8fd2ff244fb4acc3ae198b2bc7 internal/cli/authors/retrieve.go: last_write_checksum: sha1:fd4a28868414d15ce664f8f632e3da12bfdb4c1b internal/cli/authors/root.go: @@ -94,6 +108,10 @@ trackedFiles: last_write_checksum: sha1:3c9d297eb0c6908e4efa9ab881dfefd9909c1595 internal/cli/content/submit.go: last_write_checksum: sha1:5db89edd5a5f6b7867090c973744f629e4498422 + internal/cli/feedback/create.go: + last_write_checksum: sha1:1e22b74d4129da1cc98e9500a4e279f825d49567 + internal/cli/feedback/root.go: + last_write_checksum: sha1:6624f6b43792d8c7cb2e843d4ebd8e858a552fd0 internal/cli/masking.go: last_write_checksum: sha1:5a69fa055d628c1b8507cd123f3cd18309aa9a53 internal/cli/projects/apikeys/create.go: @@ -115,23 +133,27 @@ trackedFiles: internal/cli/projects/update.go: last_write_checksum: sha1:86360b44e47dfff918db06784b723a60a2892fe7 internal/cli/queue/getstats.go: - last_write_checksum: sha1:07a64d9c7801dff4e33378b35158f450e97b79bf + last_write_checksum: sha1:dccfb6a53167375a75d7a41b243f93e3938fb475 internal/cli/queue/items/list.go: - last_write_checksum: sha1:268514839cdea6a17f245e05f5aba01bf3b3c409 + last_write_checksum: sha1:9472e50b9528c24b556b64d4acd269dfcbffbde0 internal/cli/queue/items/resolve.go: - last_write_checksum: sha1:e5b34d95a924eb4c54ef25196c1a2dea478f8ef2 + last_write_checksum: sha1:82554a72880ca640f629bd4e37593bd9e638b595 + internal/cli/queue/items/resolvemany.go: + last_write_checksum: sha1:6bafd91917a5fa29a12dfa448d2d8d7b72e66517 internal/cli/queue/items/root.go: - last_write_checksum: sha1:b4227abd36f2ccd8ae59fa31c6a56b41fa071f81 + last_write_checksum: sha1:b90044575e57963575795c052a3e3b20b5bccf1f internal/cli/queue/items/unresolve.go: - last_write_checksum: sha1:9c478d164888e7cdedf87258cf71850cd55885d4 + last_write_checksum: sha1:2ca0793ed1596af844d545e617ae27f902f19186 + internal/cli/queue/list.go: + last_write_checksum: sha1:6c90a4b14db41fdc45b41c4b8a05adb9159e109a internal/cli/queue/retrieve.go: - last_write_checksum: sha1:4e149ec32600717d2f99df56a979d38a73ec6387 + last_write_checksum: sha1:53c36bb09e0f2bccc9b54b2988a2a2b20f25b55f internal/cli/queue/root.go: - last_write_checksum: sha1:f0bee830cf9df88a62014a13e4b2992d6a4a4b33 + last_write_checksum: sha1:ce2788b28359a23ff98a244ac90b71a827c16e63 internal/cli/root.go: - last_write_checksum: sha1:d00799a3804f87479242f16bf520cb593f79ba8e + last_write_checksum: sha1:1bc5de6fbf6e903d6ce21034fbfd82719e4bd5e3 internal/cli/version.go: - last_write_checksum: sha1:a8ab87a20abe46d2c0f4928cafee3c713021c841 + last_write_checksum: sha1:3d31261c59e8a154342e9ad6376ff39e5e54b2f7 internal/cli/webhooks/create.go: last_write_checksum: sha1:98c73266c8954efcf0da2493ae41909df45a38f0 internal/cli/webhooks/delete.go: @@ -150,6 +172,8 @@ trackedFiles: last_write_checksum: sha1:9745a1744bf952513bfc6a014f526425190bc81b internal/cli/whoami.go: last_write_checksum: sha1:1cf0058687544cf792329d1944330e3a86b2bb7d + internal/cli/wordlist/create.go: + last_write_checksum: sha1:40c5b64716f2bcadf2d668ac1e0d63137780b288 internal/cli/wordlist/getembeddingstatus.go: last_write_checksum: sha1:127a47ac52060279377b439b9d53722ff2ce3836 internal/cli/wordlist/list.go: @@ -157,9 +181,9 @@ trackedFiles: internal/cli/wordlist/retrieve.go: last_write_checksum: sha1:12b90a87f77658b316d8f66a8f31159da76e909c internal/cli/wordlist/root.go: - last_write_checksum: sha1:cfe134c26636229d8a209301922a0cbd1127df79 + last_write_checksum: sha1:ad86a0ad77b173379c1d373cf95d33996e2fe88c internal/cli/wordlist/update.go: - last_write_checksum: sha1:a0bbab949578efb64760a3347553474f69262d15 + last_write_checksum: sha1:f59d133aa483d707135b86e4239a9d0d962e0170 internal/cli/wordlist/words/add.go: last_write_checksum: sha1:cb614fe35c79ab3e124d1eacf56066899e68ce92 internal/cli/wordlist/words/remove.go: @@ -228,16 +252,20 @@ trackedFiles: last_write_checksum: sha1:c73d83f641b59a97479506597334a95539d34e16 internal/sdk/actions.go: last_write_checksum: sha1:d6f0d54d24673ae13bd908680018b71e7d520820 + internal/sdk/analytics.go: + last_write_checksum: sha1:b33d5be04b4ba31ea97e2b4a23af2ba00869da59 internal/sdk/apikeys.go: last_write_checksum: sha1:eb2143b8ecfa3044b599462841bd90d70671bf3b internal/sdk/authors.go: - last_write_checksum: sha1:01eb0e9f6fa3fd9987edb54429293271203c7ec6 + last_write_checksum: sha1:6634e98d71de59478161a5e9e0ce21c9109536af internal/sdk/channels.go: last_write_checksum: sha1:9725f5e3d5a0f5de727883345c47910195549294 internal/sdk/content.go: last_write_checksum: sha1:e8867fac2baae5fb6d85d330f8411add76c594e7 + internal/sdk/feedback.go: + last_write_checksum: sha1:e851165bd315e8c590b4df1a5b6d40d4e2fa4a92 internal/sdk/items.go: - last_write_checksum: sha1:269a7cd14e98c407bd146153ae55af2f38918a8c + last_write_checksum: sha1:6da4b4e01374a5310ddd040e15cef9ec9c02d58f internal/sdk/models/components/authenticationerror.go: last_write_checksum: sha1:a3e65f0561efe3e332c01c0966f9b34f017e92eb internal/sdk/models/components/badrequesterror.go: @@ -253,7 +281,7 @@ trackedFiles: internal/sdk/models/components/internalservererror.go: last_write_checksum: sha1:8b35293466b913ecc37d2494b30fe7cd03b0b995 internal/sdk/models/components/notfounderror.go: - last_write_checksum: sha1:e75af606cf0e2e882177c0d1fffd8c3acd31c7c5 + last_write_checksum: sha1:f84dec44b5c02cc149da3039d85fa604419628e8 internal/sdk/models/components/pagination.go: last_write_checksum: sha1:97f4268803fbaed3c171c608d9213ede63739940 internal/sdk/models/components/permissiondeniederror.go: @@ -265,7 +293,13 @@ trackedFiles: internal/sdk/models/components/projectwithkey.go: last_write_checksum: sha1:a8186b049b303d92ef01688eca3e05ea9dabef2b internal/sdk/models/components/publicauthor.go: - last_write_checksum: sha1:35015ced72611bbb8452e4a0403c1efec51d7eb0 + last_write_checksum: sha1:1666c306edc5a0e15419e2e3bfa5993a99f086e9 + internal/sdk/models/components/queue.go: + last_write_checksum: sha1:7fbe5aa0d9bf34eacb6eeb3dd1c3621d6c44b1d0 + internal/sdk/models/components/queueitem.go: + last_write_checksum: sha1:2cd9946435953cc326610016dee84c1ac5e9ed66 + internal/sdk/models/components/queuewithcounts.go: + last_write_checksum: sha1:7955e2097d8a045e8c91fc7e95a03ebfe32d6286 internal/sdk/models/components/ratelimiterror.go: last_write_checksum: sha1:95845bf1abbaf4bf179e7ab3a32262bf3e15a128 internal/sdk/models/components/security.go: @@ -278,12 +312,16 @@ trackedFiles: last_write_checksum: sha1:3c1c999156631ee94c86983931480460f1330f8d internal/sdk/models/operations/createchannel.go: last_write_checksum: sha1:7418ba8327dcfd0855b6e1eb972bd8581a468635 + internal/sdk/models/operations/createfeedback.go: + last_write_checksum: sha1:6f1ef73a912b29ddcd14d8e6a6486ed436a4a694 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/createwordlist.go: + last_write_checksum: sha1:be2e69e7b2d511490b09a81d9e66c65007ffe4fd internal/sdk/models/operations/deleteaction.go: last_write_checksum: sha1:e06dc2aeeecb7a5dcf6f13c449ceb7c8b7bad1cd internal/sdk/models/operations/deleteauthor.go: @@ -304,6 +342,18 @@ trackedFiles: last_write_checksum: sha1:7b0caea7bdc17e48f831fdc4388fc4d29989c75c internal/sdk/models/operations/getaction.go: last_write_checksum: sha1:ddfe9af1f4fd083b2dc97e995077ef244ff11dae + internal/sdk/models/operations/getanalyticsauthors.go: + last_write_checksum: sha1:3fa19de87f00accc812e0a16c521fa57d92adc35 + internal/sdk/models/operations/getanalyticsautomation.go: + last_write_checksum: sha1:b0c557808209bca879b5700a3f2a7e3171a25e77 + internal/sdk/models/operations/getanalyticschannels.go: + last_write_checksum: sha1:541775eecd8b0ebb6ca64eab40355ec91bde583d + internal/sdk/models/operations/getanalyticspolicies.go: + last_write_checksum: sha1:7bb94d88343eecf536fe57a6a804871a56e4fc0f + internal/sdk/models/operations/getanalyticssummary.go: + last_write_checksum: sha1:36974d01efb5deb7975e3e9f17e5e910091a1a8d + internal/sdk/models/operations/getanalyticsvolumes.go: + last_write_checksum: sha1:8e138d6643add7328c31f3a9b176d90f3791164b internal/sdk/models/operations/getauthor.go: last_write_checksum: sha1:4b82d2edd4b504b9b87b92477f8435dd6314b634 internal/sdk/models/operations/getchannel.go: @@ -311,9 +361,9 @@ trackedFiles: internal/sdk/models/operations/getproject.go: last_write_checksum: sha1:48cc0fd6db458377c6d0ad3a91e1f198b10b8325 internal/sdk/models/operations/getqueue.go: - last_write_checksum: sha1:7a286d4d135cdabd75932a2f772609424647b44b + last_write_checksum: sha1:e486313087a95080bf461814d79b3b020ed941ce internal/sdk/models/operations/getqueuestats.go: - last_write_checksum: sha1:666f4a5f9eedffe916ccbe66a9324be8ff4e945a + last_write_checksum: sha1:c5e3e3b390def46b778894d2806572cbfba56f4c internal/sdk/models/operations/getwebhook.go: last_write_checksum: sha1:d01d9d7f85531e2a959344b8cf568c0fe1bd9c65 internal/sdk/models/operations/getwebhooksecret.go: @@ -325,13 +375,15 @@ trackedFiles: internal/sdk/models/operations/listactions.go: last_write_checksum: sha1:12d64ab54de1d63e93071b436b80ea97aa62ed6a internal/sdk/models/operations/listauthors.go: - last_write_checksum: sha1:bbbb500de27c99fe9912b5eeb98f6af266387444 + last_write_checksum: sha1:7eae1f5b1ea69b7b626a0ac34312cf25dd769ab5 internal/sdk/models/operations/listchannels.go: last_write_checksum: sha1:552e19679de72238e62a01a8e08d94a4c7663072 internal/sdk/models/operations/listprojects.go: last_write_checksum: sha1:a67ec0268ac80f4769cdb6e8e64e786272701386 internal/sdk/models/operations/listqueueitems.go: - last_write_checksum: sha1:0e8fc2e370b14969116b7999c37b070fb06481af + last_write_checksum: sha1:b4825650ca44b209fdbe2cce9f56592ebc60cb1a + internal/sdk/models/operations/listqueues.go: + last_write_checksum: sha1:686fcf3d244a150b4a492aa1bb44ea101734d823 internal/sdk/models/operations/listwebhooks.go: last_write_checksum: sha1:bbeeb85803ba7cc103e758831e54f29b32c392f2 internal/sdk/models/operations/listwordlists.go: @@ -341,11 +393,13 @@ trackedFiles: internal/sdk/models/operations/removewordlistwords.go: last_write_checksum: sha1:3f05a9e7df2be903164ba8bc07835473087b7f64 internal/sdk/models/operations/resolvequeueitem.go: - last_write_checksum: sha1:a7c47105ff09e5477b64dfe0969c309df2dca239 + last_write_checksum: sha1:eb416c24aa5b611629925fd8caa77f4ce33bd475 + internal/sdk/models/operations/resolvequeueitems.go: + last_write_checksum: sha1:f43daa692b1e8203ce8ebf2adf80b79e9f04b8ae internal/sdk/models/operations/submitcontent.go: - last_write_checksum: sha1:b4837d292b3031a7a78b4b85c5cd0651501e4c38 + last_write_checksum: sha1:cb670d0bf2fdcd9c7ac68031c7512e2c7d0604bf internal/sdk/models/operations/unresolvequeueitem.go: - last_write_checksum: sha1:edc2fa4a4dab4ebfed12bdf62aff61ff92c2ad1d + last_write_checksum: sha1:093fc225a3915f232e5fc18512eb0df40cfc956d internal/sdk/models/operations/updateaction.go: last_write_checksum: sha1:bdd668d1dd1d7d164eac482e10050cd4b8a80bf9 internal/sdk/models/operations/updateauthor.go: @@ -357,7 +411,7 @@ trackedFiles: internal/sdk/models/operations/updatewebhook.go: last_write_checksum: sha1:78fb29ae9eb0bca8dd9145a0069701482b5538ac internal/sdk/models/operations/updatewordlist.go: - last_write_checksum: sha1:57f668e1a6f840de5c8da36bc953202ad095f61f + last_write_checksum: sha1:5dfe7c726c07c04f510f04763517d16c928d9e8a internal/sdk/models/sdkerrors/authenticationerror.go: last_write_checksum: sha1:1c7b80e36e57c4cb1d617169c5c691ebec05cb09 internal/sdk/models/sdkerrors/badrequesterror.go: @@ -367,7 +421,7 @@ trackedFiles: internal/sdk/models/sdkerrors/internalservererror.go: last_write_checksum: sha1:71ef8076b29dc0422745330cc09e8ce243325127 internal/sdk/models/sdkerrors/notfounderror.go: - last_write_checksum: sha1:e41e2f80bc3f43feb8936430c20a68ec1feb9d54 + last_write_checksum: sha1:c4830df34d20538880fdd7728b926526c7ddc229 internal/sdk/models/sdkerrors/permissiondeniederror.go: last_write_checksum: sha1:afab0d9afb5d6fd62e298cfa54fb56ad72f2f9b2 internal/sdk/models/sdkerrors/ratelimiterror.go: @@ -377,7 +431,7 @@ trackedFiles: internal/sdk/models/sdkerrors/sdkdefaulterror.go: last_write_checksum: sha1:437b3a3eacb54d8648a68f80b3cd38fa5019f006 internal/sdk/moderationapi.go: - last_write_checksum: sha1:dddc315a89c2907ff24d91c28784ed97fe738c5f + last_write_checksum: sha1:5e3a44fff5b0c2ee7bedd9735b7436f24dd0f947 internal/sdk/optionalnullable/optionalnullable.go: last_write_checksum: sha1:b828c94c45116e632925027e4e2c211e971e04e2 internal/sdk/optionalnullable/optionalnullable_test.go: @@ -385,7 +439,7 @@ trackedFiles: internal/sdk/projects.go: last_write_checksum: sha1:77b021be8e1ea6b2d2aef13a536a922d0b614bb9 internal/sdk/queue.go: - last_write_checksum: sha1:e7ad9e07ed4c6f302ac8b7123a7c4e8d9b4e034e + last_write_checksum: sha1:ca667199d9f10fb9a132a58f93016ea11e2c15a1 internal/sdk/retry/config.go: last_write_checksum: sha1:102d1953fbd7e9f312c4442c71ccca2eaaeaa27d internal/sdk/sdkinternal/config/sdkconfiguration.go: @@ -431,15 +485,15 @@ trackedFiles: internal/sdk/webhooksecret.go: last_write_checksum: sha1:dba657bf0d16c657a8dbca1ae9f620394a15b83c internal/sdk/wordlist.go: - last_write_checksum: sha1:cd1e1acbeeaf0fb982b518502442f316b6100e68 + last_write_checksum: sha1:3e9a9d7460d5fa4594d078c798d713e1fad2c9dc internal/sdk/words.go: last_write_checksum: sha1:444eb505ec11550665c14531b19b6ef451b2f4cf internal/testclient/testclient.go: last_write_checksum: sha1:7107e59d336235c44af152c5a61ee3896a7841fa internal/usage/bodyschemas.go: - last_write_checksum: sha1:f766420e385b255888deecba3146cc31d22dc3d3 + last_write_checksum: sha1:e35857ce9ebd1c1ff7a9913f8cdb21a27aff7db3 internal/usage/schema.go: - last_write_checksum: sha1:04bfae1bd9a42fc9ffc14b31e09bff0d4c326b65 + last_write_checksum: sha1:83417c8703b1d61777c96648b8db8a955b562791 scripts/install.ps1: last_write_checksum: sha1:bd3a52c28f647ed881c6e8d72abf2d84216976c9 scripts/install.sh: @@ -553,7 +607,7 @@ examples: id: "" responses: "200": - application/json: {"queue": {"id": "", "name": "", "description": "furiously certification tighten", "filter": {}, "unresolvedItemsCount": 1559.96, "resolvedItemsCount": 1949.18, "totalItemsCount": 1912.44}} + application/json: {"queue": {"id": "", "name": "", "description": "furiously certification tighten", "isDefault": true, "unresolvedItemsCount": 1559.96, "resolvedItemsCount": 1949.18, "totalItemsCount": 1912.44}} "400": application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://actual-thyme.biz/"} "401": @@ -575,7 +629,7 @@ examples: withinDays: 30 responses: "200": - application/json: {"reviewStats": {"averageTimeToReview": 3713.81, "totalReviewed": 9855.4, "totalPending": 2413.77}, "actionStats": [{"actionId": "", "actionName": "", "count": 4507.74, "percentageOfTotal": 3785.01}], "topReviewers": [], "trends": {"dailyReviewCounts": [{"date": "2024-07-26", "count": 794.3}], "flaggedContentTrends": []}} + application/json: {"reviewStats": {"totalReviewed": 9855.4, "totalPending": 2413.77}, "topReviewers": [], "trends": {"dailyReviewCounts": [{"date": "2024-07-26", "count": 794.3}]}} "400": application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://accomplished-councilman.com/"} "401": @@ -599,7 +653,7 @@ examples: sortDirection: "desc" responses: "200": - application/json: {"data": [{"id": "", "content": "", "labels": [{"label": "", "score": 3108.5, "flagged": false}], "flagged": false, "status": "resolved", "timestamp": 79.16}], "pagination": {"page": 42752, "pageSize": 933228, "total": 306038, "totalPages": 947297, "hasMore": true}} + application/json: {"data": [{"id": "", "contentId": "", "content": {"type": "object", "data": {"key": {"type": "image"}}}, "labels": [{"label": "", "score": 3108.5, "flagged": false}], "flagged": false, "severity": 6116.19, "status": "resolved", "timestamp": 79.16, "language": "", "conversationId": null, "authorId": null, "contentType": ""}], "pagination": {"page": 42752, "pageSize": 933228, "total": 306038, "totalPages": 947297, "hasMore": true}} "400": application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://back-status.info"} "401": @@ -643,7 +697,7 @@ examples: application/json: {} responses: "200": - application/json: {"success": false, "status": "", "unresolvedAt": ""} + application/json: {"success": false, "status": "resolved", "unresolvedAt": ""} "400": application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://passionate-partridge.net"} "401": @@ -1214,4 +1268,193 @@ examples: 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"} + getAnalyticsSummary: + speakeasy-default-get-analytics-summary: + parameters: + query: + range: "30d" + responses: + "200": + application/json: {"range": "90d", "channelId": "", "totals": {"requests": 433848, "flagged": 598421, "flagRate": 2935.14, "allowed": 77552, "rejected": 763138, "sentToReview": 929850, "handledAutomatically": 506554, "flaggedButAllowed": 534731, "distinctAuthors": 148120}, "previousPeriod": {"requests": 787873, "flagged": 594999, "flagRate": 879.43, "allowed": 931019, "rejected": 234100, "sentToReview": 107365, "handledAutomatically": 837316, "flaggedButAllowed": 749172}, "pendingReview": 702903, "flagRateSpike": {"multiple": 7020.68, "recentFlagRate": 7330.75, "baselineFlagRate": 8573.96, "recentFlagged": 850480, "recentRequests": 17927}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://far-flung-e-mail.info/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://dreary-skean.org/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://blond-mechanic.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://impartial-violin.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://juvenile-label.name/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://evil-puppet.org"} + getAnalyticsPolicies: + speakeasy-default-get-analytics-policies: + parameters: + query: + range: "30d" + limit: 8 + responses: + "200": + application/json: {"range": "7d", "channelId": "", "policies": [{"label": "", "flagged": 753055, "previousFlagged": 438914}]} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://amused-freckle.com"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://tedious-muscat.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://faraway-sundae.name"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://confused-provision.name"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://supportive-restaurant.biz/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://remorseful-reasoning.name"} + getAnalyticsAuthors: + speakeasy-default-get-analytics-authors: + parameters: + query: + range: "30d" + limit: 6 + responses: + "200": + application/json: {"range": "90d", "channelId": "", "authors": [{"authorId": "", "name": "", "flagged": 894899}]} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://helpful-warming.org/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://good-ceramics.biz/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://bulky-fen.info"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://glum-cod.com"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://pleasing-plugin.name"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://mammoth-quart.org"} + getAnalyticsChannels: + speakeasy-default-get-analytics-channels: + parameters: + query: + range: "30d" + responses: + "200": + application/json: {"range": "90d", "channels": [{"id": "", "name": "", "requests": 532881, "flagged": 590382, "flagRate": 3406.46}]} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://only-hutch.info/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://voluminous-deck.biz/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://trained-switch.com/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://joyous-march.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://pertinent-traditionalism.info"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sugary-rationale.net"} + getAnalyticsVolumes: + speakeasy-default-get-analytics-volumes: + parameters: + query: + range: "30d" + responses: + "200": + application/json: {"range": "7d", "channelId": "", "bucket": "day", "volumes": []} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://forceful-swim.org/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://exalted-season.com"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://partial-tomatillo.biz"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://ripe-humidity.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://outrageous-farm.biz"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://kooky-possession.biz"} + getAnalyticsAutomation: + speakeasy-default-get-analytics-automation: + parameters: + query: + range: "30d" + responses: + "200": + application/json: {"range": "90d", "channelId": "", "flagged": 724170, "handledByRules": 973248, "handledByCasebook": 407020, "sentToReview": 546279, "mergedDuplicates": 90369, "automationRate": 5715.74, "reviewsAllowed": 813539, "reviewsRejected": 795140, "reviewsResolvedWithoutDecision": 900070, "reviewsUnresolved": 324651, "casebookOverrulings": 565973, "rules": []} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://sunny-wombat.info/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://pastel-transom.org/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://first-zebra.net"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://gummy-skyline.biz/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://warlike-veto.com"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://prime-hyphenation.org/"} + createFeedback: + speakeasy-default-create-feedback: + requestBody: + application/json: {"title": "", "description": "commonly oddly whereas onto cooperative serene oh", "type": "feature_request", "source": "api"} + responses: + "200": + application/json: {"id": "", "status": "received"} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://wry-bakeware.org/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://powerless-testimonial.org"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://esteemed-language.info"} + listQueues: + speakeasy-default-list-queues: + parameters: + query: + page: 1 + pageSize: 20 + responses: + "200": + application/json: {"data": [], "pagination": {"page": 287357, "pageSize": 75276, "total": 18025, "totalPages": 878443, "hasMore": false}} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://scientific-certification.biz"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://intent-integer.name/"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://worldly-fog.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://immaculate-toothbrush.name/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://medium-outlaw.com"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://which-tapioca.name/"} + resolveQueueItems: + speakeasy-default-resolve-queue-items: + parameters: + path: + id: "" + requestBody: + application/json: {"itemIds": ["", "", ""]} + responses: + "200": + application/json: {"resolved": 415438, "notFoundIds": ["", ""]} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://blushing-pick.name"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://moral-daddy.biz"} + "403": + application/json: {"type": "permission_error", "code": "permission_denied", "message": "Insufficient access", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://unlined-eternity.name/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://burdensome-festival.net/"} + "500": + application/json: {"type": "api_error", "code": "internal_error", "message": "Internal server error", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://kooky-haversack.com"} + createWordlist: + speakeasy-default-create-wordlist: + requestBody: + application/json: {"name": ""} + responses: + "200": + application/json: {"id": "", "name": "", "words": ["", "", ""], "createdAt": "1721740495881", "organizationId": "", "userId": "", "strict": true} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://vivid-meander.net"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://shameless-excess.net"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://breakable-insolence.info"} examplesVersion: 1.0.2 diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index 0c9a6f8..a2b7d65 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -32,7 +32,7 @@ generation: generateNewTests: true skipResponseBodyAssertions: false cli: - version: 0.4.0 + version: 0.5.0 additionalDependencies: {} agentEnvironmentDetection: true arrayFlagFormat: repeatable diff --git a/.speakeasy/openapi.json b/.speakeasy/openapi.json index db3f0fc..cabb255 100644 --- a/.speakeasy/openapi.json +++ b/.speakeasy/openapi.json @@ -35,6 +35,10 @@ "name": "Webhooks", "description": "Endpoints that receive events, and the events they receive." }, + { + "name": "Analytics", + "description": "Moderation numbers for a project, as on the dashboard overview." + }, { "name": "Account", "description": "The account that owns the API key." @@ -50,19 +54,23 @@ { "name": "Voice", "description": "Real-time voice moderation over WebSocket." + }, + { + "name": "Feedback", + "description": "Send feature requests and product feedback to the Moderation API team." } ], "externalDocs": { "url": "https://docs.moderationapi.com" }, "paths": { - "/authors": { + "/analytics/summary": { "get": { - "operationId": "listAuthors", - "summary": "List authors", - "description": "List authors with their activity metrics and reputation. The list is paginated.", + "operationId": "getAnalyticsSummary", + "summary": "Get the analytics summary", + "description": "Get the totals of the project for a time window: requests, flags, flag rate, allowed, rejected and sent to review, with the window before for the trend. Also the review queue backlog and a flag-rate spike, if there is one.", "tags": [ - "Authors" + "Analytics" ], "security": [ { @@ -72,78 +80,25 @@ "parameters": [ { "in": "query", - "name": "page", - "schema": { - "default": 1, - "description": "Page number, starting at 1.", - "type": "integer", - "minimum": 1, - "maximum": 100000 - } - }, - { - "in": "query", - "name": "pageSize", - "schema": { - "default": 20, - "description": "Number of items per page. Maximum 100.", - "type": "integer", - "minimum": 1, - "maximum": 100 - } - }, - { - "in": "query", - "name": "sortBy", + "name": "range", "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", "type": "string", "enum": [ - "trustLevel", - "violationCount", - "reportCount", - "memberSince", - "lastActive", - "contentCount", - "flaggedContentRatio", - "averageSentiment" + "24h", + "7d", + "30d", + "90d" ] } }, { "in": "query", - "name": "sortDirection", + "name": "channel", "schema": { - "default": "desc", - "description": "Sort direction", "type": "string", - "enum": [ - "asc", - "desc" - ] - } - }, - { - "in": "query", - "name": "memberSinceDate", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "lastActiveDate", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "contentTypes", - "schema": { - "type": "array", - "items": { - "type": "string" - } + "description": "A channel ID or key. Only count content from this channel. Leave out to count all channels." } }, { @@ -165,20 +120,205 @@ "schema": { "type": "object", "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PublicAuthor" + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channelId": { + "description": "The channel the numbers are for. Null when they are for all channels.", + "type": [ + "string", + "null" + ] + }, + "totals": { + "type": "object", + "properties": { + "requests": { + "type": "integer", + "minimum": 0, + "description": "Requests received. Every request counts, also repeats and edits of the same content." + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Requests that were flagged." + }, + "flagRate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The share of requests that were flagged: flagged / requests. 0 when there were no requests." + }, + "allowed": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"allow\". Includes content that was not flagged." + }, + "rejected": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"reject\". All of them were flagged." + }, + "sentToReview": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"review\". This counts requests, not the deduplicated items in the review queue." + }, + "handledAutomatically": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that were settled without human review: rejected plus flaggedButAllowed." + }, + "flaggedButAllowed": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that were allowed without human review." + }, + "distinctAuthors": { + "type": "integer", + "minimum": 0, + "description": "Distinct authors that sent content in the window. Approximate." + } }, - "description": "The items on this page." + "required": [ + "requests", + "flagged", + "flagRate", + "allowed", + "rejected", + "sentToReview", + "handledAutomatically", + "flaggedButAllowed", + "distinctAuthors" + ], + "additionalProperties": false, + "description": "Request counts for the window. A flagged request has one recommendation: allow, reject or review." }, - "pagination": { - "$ref": "#/components/schemas/Pagination" + "previousPeriod": { + "type": "object", + "properties": { + "requests": { + "type": "integer", + "minimum": 0, + "description": "Requests received. Every request counts, also repeats and edits of the same content." + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Requests that were flagged." + }, + "flagRate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The share of requests that were flagged: flagged / requests. 0 when there were no requests." + }, + "allowed": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"allow\". Includes content that was not flagged." + }, + "rejected": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"reject\". All of them were flagged." + }, + "sentToReview": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"review\". This counts requests, not the deduplicated items in the review queue." + }, + "handledAutomatically": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that were settled without human review: rejected plus flaggedButAllowed." + }, + "flaggedButAllowed": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that were allowed without human review." + } + }, + "required": [ + "requests", + "flagged", + "flagRate", + "allowed", + "rejected", + "sentToReview", + "handledAutomatically", + "flaggedButAllowed" + ], + "additionalProperties": false, + "description": "The same counts for the window of equal length just before this one. Compare with `totals` to see the trend." + }, + "pendingReview": { + "type": "integer", + "minimum": 0, + "description": "Items in the default review queue that nobody has reviewed yet, counted as the queue counts them (repeats count once). The queue looks back 30 days, so older items are not included. The range does not change it." + }, + "flagRateSpike": { + "anyOf": [ + { + "type": "object", + "properties": { + "multiple": { + "type": "number", + "description": "How many times higher the recent flag rate is than the baseline, for example 2.4." + }, + "recentFlagRate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The flag rate in the last 2 days." + }, + "baselineFlagRate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The flag rate in the 30 days before that." + }, + "recentFlagged": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests in the last 2 days." + }, + "recentRequests": { + "type": "integer", + "minimum": 0, + "description": "Requests in the last 2 days." + } + }, + "required": [ + "multiple", + "recentFlagRate", + "baselineFlagRate", + "recentFlagged", + "recentRequests" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ], + "description": "Set when the flag rate of the last 2 days is at least 2 times the rate of the 30 days before, with at least 50 flagged requests. Null when there is no spike. Always for the full project: the range and channel do not change it." } }, "required": [ - "data", - "pagination" + "range", + "channelId", + "totals", + "previousPeriod", + "pendingReview", + "flagRateSpike" ], "additionalProperties": false } @@ -246,42 +386,25 @@ } } }, - "x-speakeasy-group": "authors", - "x-speakeasy-name-override": "list", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "summary", "x-speakeasy-mcp": { - "title": "List authors", + "title": "Get the analytics summary", "scopes": [ "read" ], "readOnlyHint": true, "destructiveHint": false - }, - "x-speakeasy-pagination": { - "type": "offsetLimit", - "inputs": [ - { - "name": "page", - "in": "parameters", - "type": "page" - }, - { - "name": "pageSize", - "in": "parameters", - "type": "limit" - } - ], - "outputs": { - "results": "$.data", - "numPages": "$.pagination.totalPages" - } } - }, - "post": { - "operationId": "createAuthor", - "summary": "Create a new author", - "description": "Create a new author. Typically not needed as authors are created automatically when content is moderated.", + } + }, + "/analytics/policies": { + "get": { + "operationId": "getAnalyticsPolicies", + "summary": "Get the most flagged policies", + "description": "Get the policy labels that flagged the most content in a time window, largest first, with the count in the window before.", "tags": [ - "Authors" + "Analytics" ], "security": [ { @@ -289,6 +412,40 @@ } ], "parameters": [ + { + "in": "query", + "name": "range", + "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ] + } + }, + { + "in": "query", + "name": "channel", + "schema": { + "type": "string", + "description": "A channel ID or key. Only count content from this channel. Leave out to count all channels." + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "default": 8, + "description": "How many entries to return, largest first. 1 to 50.", + "type": "integer", + "minimum": 1, + "maximum": 50 + } + }, { "in": "header", "name": "Project-Id", @@ -300,147 +457,67 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "profile_picture": { - "description": "URL of the author's profile picture", - "anyOf": [ - { - "type": "string", - "format": "uri" + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channelId": { + "description": "The channel the numbers are for. Null when they are for all channels.", + "type": [ + "string", + "null" + ] + }, + "policies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "The policy label, for example `toxicity`." + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests with this label in the window. Repeats count each time." + }, + "previousFlagged": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests with this label in the window of equal length before." + } + }, + "required": [ + "label", + "flagged", + "previousFlagged" + ], + "additionalProperties": false }, - { - "type": "null" - } - ] + "description": "The policy labels with the most flagged requests, largest first." + } }, - "external_link": { - "description": "URL of the author's external profile", - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] - }, - "name": { - "description": "Author name or identifier", - "type": [ - "string", - "null" - ] - }, - "email": { - "description": "Author email address", - "anyOf": [ - { - "type": "string", - "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": "null" - } - ] - }, - "company": { - "description": "The author's company or organization", - "type": [ - "string", - "null" - ] - }, - "metadata": { - "type": "object", - "properties": { - "email_verified": { - "description": "Whether the author's email is verified", - "type": [ - "boolean", - "null" - ] - }, - "phone_verified": { - "description": "Whether the author's phone number 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" - ] - } - }, - "additionalProperties": {}, - "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." - }, - "first_seen": { - "type": "number", - "description": "Timestamp when author first appeared" - }, - "last_seen": { - "type": "number", - "description": "Timestamp of last activity" - }, - "manual_trust_level": { - "anyOf": [ - { - "type": "number", - "minimum": -1, - "maximum": 4 - }, - { - "type": "null" - } - ] - }, - "external_id": { - "type": "string", - "description": "External ID of the user, typically the ID of the author in your database." - } - }, - "required": [ - "external_id" - ] - }, - "example": { - "external_id": "user-1092", - "name": "Jamie Rivera", - "email": "jamie@example.com", - "metadata": { - "email_verified": true, - "is_paying_customer": false - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicAuthor" + "required": [ + "range", + "channelId", + "policies" + ], + "additionalProperties": false } } } @@ -475,6 +552,16 @@ } } }, + "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": { @@ -496,25 +583,25 @@ } } }, - "x-speakeasy-group": "authors", - "x-speakeasy-name-override": "create", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "policies", "x-speakeasy-mcp": { - "title": "Create a new author", + "title": "Get the most flagged policies", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false } } }, - "/authors/{id}": { + "/analytics/authors": { "get": { - "operationId": "getAuthor", - "summary": "Get author details", - "description": "Get detailed information about a specific author including historical data and analysis", + "operationId": "getAnalyticsAuthors", + "summary": "Get the most flagged authors", + "description": "Get the authors with the most flagged content in a time window, largest first.", "tags": [ - "Authors" + "Analytics" ], "security": [ { @@ -523,14 +610,38 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "range", "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", "type": "string", - "description": "Either external ID or the ID assigned by moderation API." - }, - "required": true, - "description": "Either external ID or the ID assigned by moderation API." + "enum": [ + "24h", + "7d", + "30d", + "90d" + ] + } + }, + { + "in": "query", + "name": "channel", + "schema": { + "type": "string", + "description": "A channel ID or key. Only count content from this channel. Leave out to count all channels." + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "default": 6, + "description": "How many entries to return, largest first. 1 to 50.", + "type": "integer", + "minimum": 1, + "maximum": 50 + } }, { "in": "header", @@ -549,7 +660,63 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicAuthor" + "type": "object", + "properties": { + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channelId": { + "description": "The channel the numbers are for. Null when they are for all channels.", + "type": [ + "string", + "null" + ] + }, + "authors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "authorId": { + "type": "string", + "description": "The author ID from your system. Use it with the authors endpoints." + }, + "name": { + "description": "The author name, if known.", + "type": [ + "string", + "null" + ] + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests from this author in the window." + } + }, + "required": [ + "authorId", + "name", + "flagged" + ], + "additionalProperties": false + }, + "description": "The authors with the most flagged requests, largest first. Requests without an author are not included." + } + }, + "required": [ + "range", + "channelId", + "authors" + ], + "additionalProperties": false } } } @@ -615,23 +782,25 @@ } } }, - "x-speakeasy-group": "authors", - "x-speakeasy-name-override": "retrieve", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "authors", "x-speakeasy-mcp": { - "title": "Get author details", + "title": "Get the most flagged authors", "scopes": [ "read" ], "readOnlyHint": true, "destructiveHint": false } - }, - "put": { - "operationId": "updateAuthor", - "summary": "Update author details", - "description": "Update the details of a specific author", + } + }, + "/analytics/channels": { + "get": { + "operationId": "getAnalyticsChannels", + "summary": "Get analytics per channel", + "description": "Get requests, flags and flag rate for each channel of the project in a time window.", "tags": [ - "Authors" + "Analytics" ], "security": [ { @@ -640,14 +809,19 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "range", "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", "type": "string", - "description": "Either external ID or the ID assigned by moderation API." - }, - "required": true, - "description": "Either external ID or the ID assigned by moderation API." + "enum": [ + "24h", + "7d", + "30d", + "90d" + ] + } }, { "in": "header", @@ -660,137 +834,71 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "profile_picture": { - "description": "URL of the author's profile picture", - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] - }, - "external_link": { - "description": "URL of the author's external profile", - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] - }, - "name": { - "description": "Author name or identifier", - "type": [ - "string", - "null" - ] - }, - "email": { - "description": "Author email address", - "anyOf": [ - { - "type": "string", - "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": "null" - } - ] - }, - "company": { - "description": "The author's company or organization", - "type": [ - "string", - "null" - ] - }, - "metadata": { - "type": "object", - "properties": { - "email_verified": { - "description": "Whether the author's email is verified", - "type": [ - "boolean", - "null" - ] - }, - "phone_verified": { - "description": "Whether the author's phone number 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" - ] - } - }, - "additionalProperties": {}, - "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." - }, - "first_seen": { - "type": "number", - "description": "Timestamp when author first appeared" - }, - "last_seen": { - "type": "number", - "description": "Timestamp of last activity" - }, - "manual_trust_level": { - "anyOf": [ - { - "type": "number", - "minimum": -1, - "maximum": 4 - }, - { - "type": "null" - } - ] - } - } - }, - "example": { - "manual_trust_level": 2, - "metadata": { - "identity_verified": true - } - } - } - } - }, "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicAuthor" + "type": "object", + "properties": { + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The channel ID." + }, + "name": { + "type": "string", + "description": "The channel name." + }, + "requests": { + "type": "integer", + "minimum": 0, + "description": "Requests received in this channel." + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Requests flagged in this channel." + }, + "flagRate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The share of requests in this channel that were flagged: flagged / requests." + } + }, + "required": [ + "id", + "name", + "requests", + "flagged", + "flagRate" + ], + "additionalProperties": false + }, + "description": "Requests and flags per channel. Channels with no requests in the window are not included." + } + }, + "required": [ + "range", + "channels" + ], + "additionalProperties": false } } } @@ -856,23 +964,25 @@ } } }, - "x-speakeasy-group": "authors", - "x-speakeasy-name-override": "update", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "channels", "x-speakeasy-mcp": { - "title": "Update author details", + "title": "Get analytics per channel", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false } - }, - "delete": { - "operationId": "deleteAuthor", - "summary": "Delete an author", - "description": "Delete a specific author. This resets the author: status, blocks, trust level, metrics and action history are removed. The author is created again with a clean record the next time content is moderated for the same ID.", + } + }, + "/analytics/volumes": { + "get": { + "operationId": "getAnalyticsVolumes", + "summary": "Get volumes over time", + "description": "Get requests, flags, rejections and reviews per hour (24h) or per day (7d, 30d, 90d), oldest first.", "tags": [ - "Authors" + "Analytics" ], "security": [ { @@ -881,14 +991,27 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "range", "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", "type": "string", - "description": "Either external ID or the ID assigned by moderation API." - }, - "required": true, - "description": "Either external ID or the ID assigned by moderation API." + "enum": [ + "24h", + "7d", + "30d", + "90d" + ] + } + }, + { + "in": "query", + "name": "channel", + "schema": { + "type": "string", + "description": "A channel ID or key. Only count content from this channel. Leave out to count all channels." + } }, { "in": "header", @@ -909,12 +1032,78 @@ "schema": { "type": "object", "properties": { - "success": { - "type": "boolean" + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channelId": { + "description": "The channel the numbers are for. Null when they are for all channels.", + "type": [ + "string", + "null" + ] + }, + "bucket": { + "type": "string", + "enum": [ + "hour", + "day" + ], + "description": "The size of each entry in `volumes`." + }, + "volumes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "string", + "description": "Start of the bucket, as an ISO 8601 timestamp in UTC." + }, + "requests": { + "type": "integer", + "minimum": 0, + "description": "Requests received in the bucket." + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Requests flagged in the bucket." + }, + "rejected": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"reject\" in the bucket." + }, + "sentToReview": { + "type": "integer", + "minimum": 0, + "description": "Requests with the recommendation \"review\" in the bucket." + } + }, + "required": [ + "start", + "requests", + "flagged", + "rejected", + "sentToReview" + ], + "additionalProperties": false + }, + "description": "Counts over time, oldest first. Buckets with no requests are included with zeros." } }, "required": [ - "success" + "range", + "channelId", + "bucket", + "volumes" ], "additionalProperties": false } @@ -982,25 +1171,25 @@ } } }, - "x-speakeasy-group": "authors", - "x-speakeasy-name-override": "delete", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "volumes", "x-speakeasy-mcp": { - "title": "Delete an author", + "title": "Get volumes over time", "scopes": [ - "destructive" + "read" ], - "readOnlyHint": false, - "destructiveHint": true + "readOnlyHint": true, + "destructiveHint": false } } }, - "/queue/{id}": { + "/analytics/automation": { "get": { - "operationId": "getQueue", - "summary": "Get a queue", - "description": "Get a queue", + "operationId": "getAnalyticsAutomation", + "summary": "Get the automation breakdown", + "description": "Get how flagged content was handled in a time window: settled by rules or the casebook, or sent to human review, and what the reviewers decided.", "tags": [ - "Review queues" + "Analytics" ], "security": [ { @@ -1009,14 +1198,27 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "range", "schema": { + "default": "30d", + "description": "The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets.", "type": "string", - "description": "The queue ID" - }, - "required": true, - "description": "The queue ID" + "enum": [ + "24h", + "7d", + "30d", + "90d" + ] + } + }, + { + "in": "query", + "name": "channel", + "schema": { + "type": "string", + "description": "A channel ID or key. Only count content from this channel. Leave out to count all channels." + } }, { "in": "header", @@ -1037,288 +1239,125 @@ "schema": { "type": "object", "properties": { - "queue": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" + "range": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d", + "90d" + ], + "description": "The time window of the numbers." + }, + "channelId": { + "description": "The channel the numbers are for. Null when they are for all channels.", + "type": [ + "string", + "null" + ] + }, + "flagged": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests in the window. Counted on a rolling window from the raw requests, so it can be a little different from the summary." + }, + "handledByRules": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that a rule allowed or rejected without human review." + }, + "handledByCasebook": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that the casebook settled without human review." + }, + "sentToReview": { + "type": "integer", + "minimum": 0, + "description": "Distinct content items sent to the review queue. Repeats of the same content count once, as in the queue." + }, + "mergedDuplicates": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that the review queue merged into an item it already shows: repeats, edits and content that was settled later." + }, + "automationRate": { + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "The share of flagged requests that did not need their own human review: (flagged - sentToReview) / flagged." }, - "filter": { - "type": "object", - "properties": { - "isFlagged": { - "type": "string", - "enum": [ - "ALL", - "FLAGGED", - "NOT_FLAGGED", - "SHADOW_FLAGGED" - ] - }, - "casebookAnswer": { - "type": "string", - "enum": [ - "ALL", - "ALLOWED", - "REJECTED", - "NO_ANSWER" - ] - }, - "casebookAgreement": { - "type": "string", - "enum": [ - "ALL", - "OVERRULED", - "AGREED" - ] - }, - "labels": { - "type": "array", - "items": { - "type": "string" - } - }, - "filterLabels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "FLAGGED", - "NOT_FLAGGED", - "THRESHOLDS", - "MATCHED" - ] - }, - "minThreshold": { - "anyOf": [ - { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - { - "type": "null" - } - ] - }, - "maxThreshold": { - "anyOf": [ - { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "label", - "type" - ], - "additionalProperties": false - } - }, - "metadataFilters": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "value": { - "type": "string", - "maxLength": 512 - } - }, - "required": [ - "key", - "value" - ], - "additionalProperties": false - } - }, - "filteredChannelIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "filteredActionIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "languages": { - "type": "array", - "items": { - "type": "string" - } - }, - "contentTypes": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "profile", - "message", - "post", - "comment", - "event", - "product", - "review", - "voice", - "other" - ] - } - }, - "mediaTypes": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "text", - "image", - "video", - "object", - "audio" - ] - } - }, - "authorTrustLevels": { - "type": "array", - "items": { - "type": "integer", - "minimum": -1, - "maximum": 4 - } - }, - "recommendationActions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "review", - "allow", - "reject" - ] - } - }, - "minSeverity": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "maxSeverity": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "afterDate": { - "type": "string" - }, - "beforeDate": { - "type": "string" - }, - "within": { - "type": "number", - "exclusiveMinimum": 0 - }, - "withinUnit": { - "type": "string", - "enum": [ - "MINUTES", - "HOURS", - "DAYS", - "WEEKS", - "MONTHS", - "YEARS" - ] - }, - "clearDateWindow": { - "type": "boolean" - }, - "checkStatus": { - "anyOf": [ - { - "type": "string", - "enum": [ - "all", - "checked", - "unchecked" - ] - }, - { - "type": "null" - } - ] - }, - "authorID": { - "type": "string" - }, - "contentID": { - "type": "string" - }, - "conversationIds": { - "type": "array", - "items": { - "type": [ - "string", - "null" - ] - } - }, - "search": { - "type": "array", - "items": { - "type": "string" - } - } + { + "type": "null" + } + ] + }, + "reviewsAllowed": { + "type": "integer", + "minimum": 0, + "description": "Review queue items from this window that a moderator allowed." + }, + "reviewsRejected": { + "type": "integer", + "minimum": 0, + "description": "Review queue items from this window that a moderator rejected." + }, + "reviewsResolvedWithoutDecision": { + "type": "integer", + "minimum": 0, + "description": "Review queue items from this window that a moderator marked as done without allow or reject." + }, + "reviewsUnresolved": { + "type": "integer", + "minimum": 0, + "description": "Review queue items from this window that are still open." + }, + "casebookOverrulings": { + "type": "integer", + "minimum": 0, + "description": "Requests where the casebook decision was different from the decision based on severity." + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The rule name. \"Other rules\" holds all rules after the largest ones." }, - "additionalProperties": false - }, - "unresolvedItemsCount": { - "type": "number" - }, - "resolvedItemsCount": { - "type": "number" + "count": { + "type": "integer", + "minimum": 0, + "description": "Flagged requests that this rule settled." + } }, - "totalItemsCount": { - "type": "number" - } + "required": [ + "name", + "count" + ], + "additionalProperties": false }, - "required": [ - "id", - "name", - "description", - "filter", - "unresolvedItemsCount", - "resolvedItemsCount", - "totalItemsCount" - ], - "additionalProperties": false + "description": "The rules that settled flagged requests without review, largest first. The 5 largest have their own entry. The casebook is one of the rules." } }, "required": [ - "queue" + "range", + "channelId", + "flagged", + "handledByRules", + "handledByCasebook", + "sentToReview", + "mergedDuplicates", + "automationRate", + "reviewsAllowed", + "reviewsRejected", + "reviewsResolvedWithoutDecision", + "reviewsUnresolved", + "casebookOverrulings", + "rules" ], "additionalProperties": false } @@ -1386,10 +1425,10 @@ } } }, - "x-speakeasy-group": "queue", - "x-speakeasy-name-override": "retrieve", + "x-speakeasy-group": "analytics", + "x-speakeasy-name-override": "automation", "x-speakeasy-mcp": { - "title": "Get a queue", + "title": "Get the automation breakdown", "scopes": [ "read" ], @@ -1398,13 +1437,13 @@ } } }, - "/queue/{id}/stats": { + "/authors": { "get": { - "operationId": "getQueueStats", - "summary": "Get queue statistics", - "description": "Get detailed statistics about a moderation queue including review times, action counts, and trends", + "operationId": "listAuthors", + "summary": "List authors", + "description": "List the authors of the project, with their status, trust level and content metrics. The list is paginated. By default, the authors that were active most recently come first.", "tags": [ - "Review queues" + "Authors" ], "security": [ { @@ -1413,24 +1452,68 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "page", "schema": { - "type": "string", - "description": "The queue ID" - }, - "required": true, - "description": "The queue ID" + "default": 1, + "description": "Page number, starting at 1.", + "type": "integer", + "minimum": 1, + "maximum": 100000 + } }, { "in": "query", - "name": "withinDays", + "name": "pageSize", "schema": { - "default": 30, - "description": "Number of days to analyze statistics for", + "default": 20, + "description": "Number of items per page. Maximum 100.", "type": "integer", "minimum": 1, - "maximum": 365 + "maximum": 100 + } + }, + { + "in": "query", + "name": "sortBy", + "schema": { + "type": "string", + "enum": [ + "trustLevel", + "violationCount", + "memberSince", + "lastActive", + "contentCount", + "flaggedContentRatio", + "averageSentiment" + ] + } + }, + { + "in": "query", + "name": "sortDirection", + "schema": { + "default": "desc", + "description": "Sort direction", + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "in": "query", + "name": "memberSinceDate", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "lastActiveDate", + "schema": { + "type": "string" } }, { @@ -1452,182 +1535,20 @@ "schema": { "type": "object", "properties": { - "reviewStats": { - "type": "object", - "properties": { - "averageTimeToReview": { - "type": "number", - "description": "Average time in milliseconds to review an item" - }, - "totalReviewed": { - "type": "number", - "description": "Total number of items reviewed" - }, - "totalPending": { - "type": "number", - "description": "Total number of items pending review" - } - }, - "required": [ - "averageTimeToReview", - "totalReviewed", - "totalPending" - ], - "additionalProperties": false - }, - "actionStats": { - "type": "array", - "items": { - "type": "object", - "properties": { - "actionId": { - "type": "string", - "description": "ID of the moderation action" - }, - "actionName": { - "type": "string", - "description": "Name of the moderation action" - }, - "count": { - "type": "number", - "description": "Number of times this action was taken" - }, - "percentageOfTotal": { - "type": "number", - "description": "Percentage this action represents of all actions" - } - }, - "required": [ - "actionId", - "actionName", - "count", - "percentageOfTotal" - ], - "additionalProperties": false - } - }, - "topReviewers": { + "data": { "type": "array", "items": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "ID of the reviewer" - }, - "name": { - "type": "string", - "description": "Name of the reviewer" - }, - "reviewCount": { - "type": "number", - "description": "Number of items reviewed" - }, - "averageTimePerReview": { - "type": "number", - "description": "Average review time in milliseconds" - }, - "accuracyScore": { - "description": "Optional accuracy score based on review quality metrics", - "type": "number" - }, - "topActions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "actionId": { - "type": "string", - "description": "Most used action by this reviewer" - }, - "actionName": { - "type": "string", - "description": "Name of the most used action" - }, - "count": { - "type": "number", - "description": "Number of times this action was used" - } - }, - "required": [ - "actionId", - "actionName", - "count" - ], - "additionalProperties": false - }, - "description": "Most common actions taken by this reviewer" - } - }, - "required": [ - "userId", - "name", - "reviewCount", - "averageTimePerReview", - "topActions" - ], - "additionalProperties": false + "$ref": "#/components/schemas/PublicAuthor" }, - "description": "List of top reviewers and their statistics" + "description": "The items on this page." }, - "trends": { - "type": "object", - "properties": { - "dailyReviewCounts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "date": { - "type": "string", - "description": "Date in YYYY-MM-DD format" - }, - "count": { - "type": "number", - "description": "Number of reviews on this date" - } - }, - "required": [ - "date", - "count" - ], - "additionalProperties": false - } - }, - "flaggedContentTrends": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "Content flag/label" - }, - "trend": { - "type": "number", - "description": "Trend indicator (-1 to 1) showing if this type of flagged content is increasing or decreasing" - } - }, - "required": [ - "label", - "trend" - ], - "additionalProperties": false - } - } - }, - "required": [ - "dailyReviewCounts", - "flaggedContentTrends" - ], - "additionalProperties": false + "pagination": { + "$ref": "#/components/schemas/Pagination" } }, "required": [ - "reviewStats", - "actionStats", - "topReviewers", - "trends" + "data", + "pagination" ], "additionalProperties": false } @@ -1695,25 +1616,42 @@ } } }, - "x-speakeasy-group": "queue", - "x-speakeasy-name-override": "getStats", + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "list", "x-speakeasy-mcp": { - "title": "Get queue statistics", + "title": "List authors", "scopes": [ "read" ], "readOnlyHint": true, "destructiveHint": false + }, + "x-speakeasy-pagination": { + "type": "offsetLimit", + "inputs": [ + { + "name": "page", + "in": "parameters", + "type": "page" + }, + { + "name": "pageSize", + "in": "parameters", + "type": "limit" + } + ], + "outputs": { + "results": "$.data", + "numPages": "$.pagination.totalPages" + } } - } - }, - "/queue/{id}/items": { - "get": { - "operationId": "listQueueItems", - "summary": "Get queue items", - "description": "Get paginated list of items in a moderation queue with filtering options", + }, + "post": { + "operationId": "createAuthor", + "summary": "Create a new author", + "description": "Create a new author. Typically not needed as authors are created automatically when content is moderated.", "tags": [ - "Review queues" + "Authors" ], "security": [ { @@ -1722,124 +1660,9 @@ ], "parameters": [ { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "description": "The queue ID" - }, - "required": true, - "description": "The queue ID" - }, - { - "in": "query", - "name": "page", - "schema": { - "default": 1, - "description": "Page number, starting at 1.", - "type": "integer", - "minimum": 1, - "maximum": 100000 - } - }, - { - "in": "query", - "name": "pageSize", - "schema": { - "default": 20, - "description": "Number of items per page. Maximum 100.", - "type": "integer", - "minimum": 1, - "maximum": 100 - } - }, - { - "in": "query", - "name": "sortField", - "schema": { - "type": "string", - "enum": [ - "createdAt", - "severity", - "reviewedAt" - ] - } - }, - { - "in": "query", - "name": "sortDirection", - "schema": { - "default": "desc", - "description": "Sort direction", - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - }, - { - "in": "query", - "name": "conversationIds", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "in": "query", - "name": "labels", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "in": "query", - "name": "afterDate", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "beforeDate", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "includeResolved", - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "authorId", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "filteredActionIds", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "in": "header", - "name": "Project-Id", - "required": false, + "in": "header", + "name": "Project-Id", + "required": false, "schema": { "type": "string" }, @@ -1847,142 +1670,147 @@ "x-speakeasy-name-override": "projectId" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "profile_picture": { + "description": "URL of the author's profile picture", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "external_link": { + "description": "URL of the author's external profile", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "name": { + "description": "Author name or identifier", + "type": [ + "string", + "null" + ] + }, + "email": { + "description": "Author email address", + "anyOf": [ + { + "type": "string", + "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": "null" + } + ] + }, + "company": { + "description": "The author's company or organization", + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": "object", + "properties": { + "email_verified": { + "description": "Whether the author's email is verified", + "type": [ + "boolean", + "null" + ] + }, + "phone_verified": { + "description": "Whether the author's phone number 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" + ] + } + }, + "additionalProperties": {}, + "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." + }, + "first_seen": { + "type": "number", + "description": "Timestamp when author first appeared" + }, + "last_seen": { + "type": "number", + "description": "Timestamp of last activity" + }, + "manual_trust_level": { + "anyOf": [ + { + "type": "number", + "minimum": -1, + "maximum": 4 + }, + { + "type": "null" + } + ] + }, + "external_id": { + "type": "string", + "description": "External ID of the user, typically the ID of the author in your database." + } + }, + "required": [ + "external_id" + ] + }, + "example": { + "external_id": "user-1092", + "name": "Jamie Rivera", + "email": "jamie@example.com", + "metadata": { + "email_verified": true, + "is_paying_customer": false + } + } + } + } + }, "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Content ID" - }, - "content": { - "type": "string", - "description": "The content to be moderated" - }, - "language": { - "description": "Content language", - "type": "string" - }, - "labels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "Label name" - }, - "score": { - "type": "number", - "description": "Confidence score of the label" - }, - "flagged": { - "type": "boolean", - "description": "Whether this label caused a flag" - } - }, - "required": [ - "label", - "score", - "flagged" - ], - "additionalProperties": false - } - }, - "conversationId": { - "description": "Conversation ID", - "type": "string" - }, - "authorId": { - "description": "Author ID", - "type": "string" - }, - "actions": { - "description": "Action IDs taken on this item", - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Action ID" - }, - "name": { - "type": "string", - "description": "Action name" - }, - "comment": { - "description": "Action comment", - "type": "string" - }, - "timestamp": { - "type": "number", - "description": "Unix timestamp of when the action was taken" - }, - "reviewer": { - "description": "Moderator userID", - "type": "string" - } - }, - "required": [ - "id", - "name", - "timestamp" - ], - "additionalProperties": false - } - }, - "flagged": { - "type": "boolean", - "description": "Whether the item is flagged by any label" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "resolved" - ], - "description": "Status of the item" - }, - "timestamp": { - "type": "number", - "description": "Unix timestamp of when the item was created" - }, - "contentType": { - "description": "Type of the content object", - "type": "string" - } - }, - "required": [ - "id", - "content", - "labels", - "flagged", - "status", - "timestamp" - ], - "additionalProperties": false - }, - "description": "The items on this page." - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "required": [ - "data", - "pagination" - ], - "additionalProperties": false + "$ref": "#/components/schemas/PublicAuthor" } } } @@ -2017,16 +1845,6 @@ } } }, - "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": { @@ -2048,44 +1866,25 @@ } } }, - "x-speakeasy-group": "queue.items", - "x-speakeasy-name-override": "list", + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "create", "x-speakeasy-mcp": { - "title": "Get queue items", + "title": "Create a new author", "scopes": [ - "read" + "write" ], - "readOnlyHint": true, + "readOnlyHint": false, "destructiveHint": false - }, - "x-speakeasy-pagination": { - "type": "offsetLimit", - "inputs": [ - { - "name": "page", - "in": "parameters", - "type": "page" - }, - { - "name": "pageSize", - "in": "parameters", - "type": "limit" - } - ], - "outputs": { - "results": "$.data", - "numPages": "$.pagination.totalPages" - } } } }, - "/queue/{id}/items/{itemId}/resolve": { - "post": { - "operationId": "resolveQueueItem", - "summary": "Resolve a queue item", - "description": "Mark a queue item as resolved with a specific moderation action", + "/authors/{id}": { + "get": { + "operationId": "getAuthor", + "summary": "Get author details", + "description": "Get detailed information about a specific author including historical data and analysis", "tags": [ - "Review queues" + "Authors" ], "security": [ { @@ -2098,20 +1897,10 @@ "name": "id", "schema": { "type": "string", - "description": "The queue ID" - }, - "required": true, - "description": "The queue ID" - }, - { - "in": "path", - "name": "itemId", - "schema": { - "type": "string", - "description": "The item ID to resolve" + "description": "Either external ID or the ID assigned by moderation API." }, "required": true, - "description": "The item ID to resolve" + "description": "Either external ID or the ID assigned by moderation API." }, { "in": "header", @@ -2124,50 +1913,13 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "comment": { - "description": "Optional comment", - "type": "string" - } - } - }, - "example": { - "comment": "Reviewed: no violation." - } - } - } - }, "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "resolvedAt": { - "type": "string", - "description": "Timestamp when the item was resolved" - }, - "comment": { - "description": "Optional comment", - "type": "string" - } - }, - "required": [ - "success", - "resolvedAt" - ], - "additionalProperties": false + "$ref": "#/components/schemas/PublicAuthor" } } } @@ -2202,6 +1954,16 @@ } } }, + "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": { @@ -2223,25 +1985,23 @@ } } }, - "x-speakeasy-group": "queue.items", - "x-speakeasy-name-override": "resolve", + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "retrieve", "x-speakeasy-mcp": { - "title": "Resolve a queue item", + "title": "Get author details", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false } - } - }, - "/queue/{id}/items/{itemId}/unresolve": { - "post": { - "operationId": "unresolveQueueItem", - "summary": "Unresolve a queue item", - "description": "Mark a previously resolved queue item as unresolved/pending", + }, + "put": { + "operationId": "updateAuthor", + "summary": "Update author details", + "description": "Update the details of a specific author", "tags": [ - "Review queues" + "Authors" ], "security": [ { @@ -2254,20 +2014,10 @@ "name": "id", "schema": { "type": "string", - "description": "The queue ID" - }, - "required": true, - "description": "The queue ID" - }, - { - "in": "path", - "name": "itemId", - "schema": { - "type": "string", - "description": "The item ID to unresolve" + "description": "Either external ID or the ID assigned by moderation API." }, "required": true, - "description": "The item ID to unresolve" + "description": "Either external ID or the ID assigned by moderation API." }, { "in": "header", @@ -2287,14 +2037,119 @@ "schema": { "type": "object", "properties": { - "comment": { - "description": "Optional reason for unresolving the item", - "type": "string" + "profile_picture": { + "description": "URL of the author's profile picture", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "external_link": { + "description": "URL of the author's external profile", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "name": { + "description": "Author name or identifier", + "type": [ + "string", + "null" + ] + }, + "email": { + "description": "Author email address", + "anyOf": [ + { + "type": "string", + "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": "null" + } + ] + }, + "company": { + "description": "The author's company or organization", + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": "object", + "properties": { + "email_verified": { + "description": "Whether the author's email is verified", + "type": [ + "boolean", + "null" + ] + }, + "phone_verified": { + "description": "Whether the author's phone number 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" + ] + } + }, + "additionalProperties": {}, + "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." + }, + "first_seen": { + "type": "number", + "description": "Timestamp when author first appeared" + }, + "last_seen": { + "type": "number", + "description": "Timestamp of last activity" + }, + "manual_trust_level": { + "anyOf": [ + { + "type": "number", + "minimum": -1, + "maximum": 4 + }, + { + "type": "null" + } + ] } } }, "example": { - "comment": "Reopened after an appeal." + "manual_trust_level": 2, + "metadata": { + "identity_verified": true + } } } } @@ -2305,26 +2160,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "status": { - "type": "string", - "description": "New status of the item" - }, - "unresolvedAt": { - "type": "string", - "description": "Timestamp when the item was unresolved" - } - }, - "required": [ - "success", - "status", - "unresolvedAt" - ], - "additionalProperties": false + "$ref": "#/components/schemas/PublicAuthor" } } } @@ -2359,6 +2195,16 @@ } } }, + "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": { @@ -2380,25 +2226,23 @@ } } }, - "x-speakeasy-group": "queue.items", - "x-speakeasy-name-override": "unresolve", - "x-speakeasy-mcp": { - "title": "Unresolve a queue item", + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "update", + "x-speakeasy-mcp": { + "title": "Update author details", "scopes": [ - "write" + "destructive" ], "readOnlyHint": false, - "destructiveHint": false + "destructiveHint": true } - } - }, - "/actions": { - "get": { - "operationId": "listActions", - "summary": "List moderation actions", - "description": "List the moderation actions of the project. The list is paginated.", + }, + "delete": { + "operationId": "deleteAuthor", + "summary": "Delete an author", + "description": "Delete a specific author. This resets the author: status, blocks, trust level, metrics and action history are removed. The author is created again with a clean record the next time content is moderated for the same ID.", "tags": [ - "Actions" + "Authors" ], "security": [ { @@ -2407,33 +2251,14 @@ ], "parameters": [ { - "in": "query", - "name": "queueId", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "page", - "schema": { - "default": 1, - "description": "Page number, starting at 1.", - "type": "integer", - "minimum": 1, - "maximum": 100000 - } - }, - { - "in": "query", - "name": "pageSize", + "in": "path", + "name": "id", "schema": { - "default": 20, - "description": "Number of items per page. Maximum 100.", - "type": "integer", - "minimum": 1, - "maximum": 100 - } + "type": "string", + "description": "Either external ID or the ID assigned by moderation API." + }, + "required": true, + "description": "Either external ID or the ID assigned by moderation API." }, { "in": "header", @@ -2454,148 +2279,12 @@ "schema": { "type": "object", "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the action." - }, - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "createdAt": { - "description": "The date the action was created.", - "type": "string" - }, - "name": { - "type": "string", - "description": "The name of the action." - }, - "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", - "type": "string", - "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" - ] - }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", - "type": "string", - "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" - ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ], - "additionalProperties": false - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" - } - }, - "required": [ - "id", - "createdAt", - "name", - "builtIn", - "queueBehaviour", - "filterInQueueIds", - "position", - "possibleValues", - "valueRequired", - "freeText" - ], - "additionalProperties": false - }, - "description": "The items on this page." - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" + "success": { + "type": "boolean" } }, "required": [ - "data", - "pagination" + "success" ], "additionalProperties": false } @@ -2663,42 +2352,25 @@ } } }, - "x-speakeasy-group": "actions", - "x-speakeasy-name-override": "list", + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "delete", "x-speakeasy-mcp": { - "title": "List moderation actions", + "title": "Delete an author", "scopes": [ - "read" - ], - "readOnlyHint": true, - "destructiveHint": false - }, - "x-speakeasy-pagination": { - "type": "offsetLimit", - "inputs": [ - { - "name": "page", - "in": "parameters", - "type": "page" - }, - { - "name": "pageSize", - "in": "parameters", - "type": "limit" - } + "destructive" ], - "outputs": { - "results": "$.data", - "numPages": "$.pagination.totalPages" - } + "readOnlyHint": false, + "destructiveHint": true } - }, + } + }, + "/feedback": { "post": { - "operationId": "createAction", - "summary": "Create an action", - "description": "Create an action.", + "operationId": "createFeedback", + "summary": "Send feedback", + "description": "Sends a feature request, bug report or other feedback to the Moderation API team. The team reads every request and can reply by email. Limited to 5 requests per minute per organization.", "tags": [ - "Actions" + "Feedback" ], "security": [ { @@ -2724,127 +2396,57 @@ "schema": { "type": "object", "properties": { - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "name": { + "title": { "type": "string", - "description": "The name of the action." + "minLength": 3, + "maxLength": 120, + "description": "A short summary of the request." }, "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] + "type": "string", + "minLength": 10, + "maxLength": 5000, + "description": "What you tried to do, why, and what is missing or does not work. Plain text. Blank lines start new paragraphs." }, "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", + "default": "feature_request", + "description": "The kind of feedback.", "type": "string", "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" + "feature_request", + "bug", + "other" ] }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "source": { + "default": "api", + "description": "Where the feedback was written: MCP server, agent skill, CLI or API.", "type": "string", "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" + "mcp", + "skill", + "cli", + "api" ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ] - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" } }, "required": [ - "name" + "title", + "description" ] }, "example": { - "name": "Suspend for 24 hours", - "key": "suspend-24h", - "type": "AUTHOR_BLOCK_TEMP", - "queueBehaviour": "REMOVE" + "title": "Filter queue items by author trust level", + "description": "We review queue items by hand. We want to see items from new authors first, so we need a filter for trust level on the queue items list.", + "type": "feature_request", + "source": "mcp" } } } }, "responses": { "200": { - "description": "Action created successfully", + "description": "Feedback received", "content": { "application/json": { "schema": { @@ -2852,129 +2454,17 @@ "properties": { "id": { "type": "string", - "description": "The ID of the action." - }, - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "createdAt": { - "description": "The date the action was created.", - "type": "string" - }, - "name": { - "type": "string", - "description": "The name of the action." - }, - "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", - "type": "string", - "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" - ] + "description": "ID of the feedback." }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "status": { "type": "string", - "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" - ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ], - "additionalProperties": false - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" + "const": "received", + "description": "The team received the feedback." } }, "required": [ "id", - "createdAt", - "name", - "builtIn", - "queueBehaviour", - "filterInQueueIds", - "position", - "possibleValues", - "valueRequired", - "freeText" + "status" ], "additionalProperties": false } @@ -3001,16 +2491,6 @@ } } }, - "409": { - "description": "Action with this key already exists", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConflictError" - } - } - } - }, "429": { "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { @@ -3022,10 +2502,10 @@ } } }, - "x-speakeasy-group": "actions", + "x-speakeasy-group": "feedback", "x-speakeasy-name-override": "create", "x-speakeasy-mcp": { - "title": "Create an action", + "title": "Send feedback", "scopes": [ "write" ], @@ -3034,13 +2514,13 @@ } } }, - "/actions/{id}": { + "/queue": { "get": { - "operationId": "getAction", - "summary": "Get an action", - "description": "Get an action by ID.", + "operationId": "listQueues", + "summary": "List queues", + "description": "List the review queues of the project. The default queue comes first. The list is paginated. To get the item counts of a queue, use `GET /queue/{id}`.", "tags": [ - "Actions" + "Review queues" ], "security": [ { @@ -3049,14 +2529,26 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "page", "schema": { - "type": "string", - "description": "The ID of the action to get." - }, - "required": true, - "description": "The ID of the action to get." + "default": 1, + "description": "Page number, starting at 1.", + "type": "integer", + "minimum": 1, + "maximum": 100000 + } + }, + { + "in": "query", + "name": "pageSize", + "schema": { + "default": 20, + "description": "Number of items per page. Maximum 100.", + "type": "integer", + "minimum": 1, + "maximum": 100 + } }, { "in": "header", @@ -3071,137 +2563,26 @@ ], "responses": { "200": { - "description": "Action retrieved successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "The ID of the action." + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Queue" + }, + "description": "The items on this page." }, - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "createdAt": { - "description": "The date the action was created.", - "type": "string" - }, - "name": { - "type": "string", - "description": "The name of the action." - }, - "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", - "type": "string", - "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" - ] - }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", - "type": "string", - "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" - ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ], - "additionalProperties": false - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" + "pagination": { + "$ref": "#/components/schemas/Pagination" } }, "required": [ - "id", - "createdAt", - "name", - "builtIn", - "queueBehaviour", - "filterInQueueIds", - "position", - "possibleValues", - "valueRequired", - "freeText" + "data", + "pagination" ], "additionalProperties": false } @@ -3228,6 +2609,16 @@ } } }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3247,25 +2638,56 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "actions", - "x-speakeasy-name-override": "retrieve", + "x-speakeasy-group": "queue", + "x-speakeasy-name-override": "list", "x-speakeasy-mcp": { - "title": "Get an action", + "title": "List queues", "scopes": [ "read" ], "readOnlyHint": true, "destructiveHint": false + }, + "x-speakeasy-pagination": { + "type": "offsetLimit", + "inputs": [ + { + "name": "page", + "in": "parameters", + "type": "page" + }, + { + "name": "pageSize", + "in": "parameters", + "type": "limit" + } + ], + "outputs": { + "results": "$.data", + "numPages": "$.pagination.totalPages" + } } - }, - "put": { - "operationId": "updateAction", - "summary": "Update an action", - "description": "Update an action.", + } + }, + "/queue/{id}": { + "get": { + "operationId": "getQueue", + "summary": "Get a queue", + "description": "Get a review queue with its item counts. The counts use the filters and the date window of the queue, the same as the dashboard.", "tags": [ - "Actions" + "Review queues" ], "security": [ { @@ -3278,10 +2700,10 @@ "name": "id", "schema": { "type": "string", - "description": "The ID of the action to update." + "description": "The queue ID." }, "required": true, - "description": "The ID of the action to update." + "description": "The queue ID." }, { "in": "header", @@ -3294,255 +2716,20 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string", - "description": "The name of the action." - }, - "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", - "type": "string", - "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" - ] - }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", - "type": "string", - "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" - ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ] - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Action updated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the action." - }, - "key": { - "description": "User defined key of the action.", - "type": [ - "string", - "null" - ] - }, - "createdAt": { - "description": "The date the action was created.", - "type": "string" - }, - "name": { - "type": "string", - "description": "The name of the action." - }, - "description": { - "description": "The description of the action.", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The type of the action.", - "anyOf": [ - { - "type": "string", - "enum": [ - "AUTHOR_BLOCK", - "AUTHOR_BLOCK_TEMP", - "AUTHOR_UNBLOCK", - "AUTHOR_DELETE", - "AUTHOR_REPORT", - "AUTHOR_WARN", - "AUTHOR_CUSTOM", - "ITEM_REJECT", - "ITEM_ALLOW", - "ITEM_CUSTOM" - ] - }, - { - "type": "null" - } - ] - }, - "builtIn": { - "default": false, - "description": "Whether the action is a built-in action or a custom one.", - "type": [ - "boolean", - "null" - ] - }, - "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.", - "type": "string", - "enum": [ - "REMOVE", - "ADD", - "NO_CHANGE" - ] - }, - "filterInQueueIds": { - "default": [], - "description": "The IDs of the queues the action is available in.", - "type": "array", - "items": { - "type": "string" - } - }, - "position": { - "default": "ALL_QUEUES", - "description": "Show the action in all queues, selected queues or no queues (to use via API only).", - "type": "string", - "enum": [ - "ALL_QUEUES", - "SOME_QUEUES", - "HIDDEN" - ] - }, - "possibleValues": { - "default": [], - "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The value of the action." - } - }, - "required": [ - "value" - ], - "additionalProperties": false - } - }, - "valueRequired": { - "default": false, - "description": "Whether the action requires a value to be executed.", - "type": "boolean" - }, - "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" - } + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "queue": { + "$ref": "#/components/schemas/QueueWithCounts" + } }, "required": [ - "id", - "createdAt", - "name", - "builtIn", - "queueBehaviour", - "filterInQueueIds", - "position", - "possibleValues", - "valueRequired", - "freeText" + "queue" ], "additionalProperties": false } @@ -3569,22 +2756,22 @@ } } }, - "404": { - "description": "Not found", + "403": { + "description": "Insufficient access", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "$ref": "#/components/schemas/PermissionDeniedError" } } } }, - "409": { - "description": "Action with this key already exists", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ConflictError" + "$ref": "#/components/schemas/NotFoundError" } } } @@ -3598,25 +2785,37 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "actions", - "x-speakeasy-name-override": "update", + "x-speakeasy-group": "queue", + "x-speakeasy-name-override": "retrieve", "x-speakeasy-mcp": { - "title": "Update an action", + "title": "Get a queue", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false } - }, - "delete": { - "operationId": "deleteAction", - "summary": "Delete an action", - "description": "Delete an action and all of its webhooks.", + } + }, + "/queue/{id}/stats": { + "get": { + "operationId": "getQueueStats", + "summary": "Get queue statistics", + "description": "Get review statistics of a queue for a period: resolved and pending items, the top reviewers and the resolved items per day.", "tags": [ - "Actions" + "Review queues" ], "security": [ { @@ -3629,10 +2828,21 @@ "name": "id", "schema": { "type": "string", - "description": "The ID of the action to delete." + "description": "The queue ID." }, "required": true, - "description": "The ID of the action to delete." + "description": "The queue ID." + }, + { + "in": "query", + "name": "withinDays", + "schema": { + "default": 30, + "description": "Length of the period in days, up to now.", + "type": "integer", + "minimum": 1, + "maximum": 365 + } }, { "in": "header", @@ -3647,24 +2857,99 @@ ], "responses": { "200": { - "description": "Action deleted successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "deleted": { - "type": "boolean", - "description": "Whether the action was deleted." + "reviewStats": { + "type": "object", + "properties": { + "totalReviewed": { + "type": "integer", + "description": "Number of items resolved in this queue in the period." + }, + "totalPending": { + "type": "integer", + "description": "Number of items submitted in the period that wait for review now." + } + }, + "required": [ + "totalReviewed", + "totalPending" + ], + "additionalProperties": false }, - "id": { - "type": "string", - "description": "The ID of the action." + "topReviewers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "userId": { + "description": "The user ID of the reviewer. `null` for items resolved with an API key.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "The name of the reviewer.", + "type": [ + "string", + "null" + ] + }, + "reviewCount": { + "type": "integer", + "description": "Number of items the reviewer resolved in the period." + } + }, + "required": [ + "userId", + "name", + "reviewCount" + ], + "additionalProperties": false + }, + "description": "Reviewers with the most resolved items, most first. Up to 10." + }, + "trends": { + "type": "object", + "properties": { + "dailyReviewCounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Date in YYYY-MM-DD format (UTC)." + }, + "count": { + "type": "integer", + "description": "Number of items resolved." + } + }, + "required": [ + "date", + "count" + ], + "additionalProperties": false + }, + "description": "Resolved items per day. Days with no reviews are left out." + } + }, + "required": [ + "dailyReviewCounts" + ], + "additionalProperties": false } }, "required": [ - "deleted", - "id" + "reviewStats", + "topReviewers", + "trends" ], "additionalProperties": false } @@ -3691,6 +2976,16 @@ } } }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3710,27 +3005,37 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "actions", - "x-speakeasy-name-override": "delete", + "x-speakeasy-group": "queue", + "x-speakeasy-name-override": "getStats", "x-speakeasy-mcp": { - "title": "Delete an action", + "title": "Get queue statistics", "scopes": [ - "destructive" + "read" ], - "readOnlyHint": false, - "destructiveHint": true + "readOnlyHint": true, + "destructiveHint": false } } }, - "/actions/execute": { - "post": { - "operationId": "executeAction", - "summary": "Execute moderation action", - "description": "Execute a moderation action on one or more content items.", + "/queue/{id}/items": { + "get": { + "operationId": "listQueueItems", + "summary": "List queue items", + "description": "List the items of a review queue, with the filters and the date window of the queue applied. By default only pending items. The list is paginated. Use the item `id` to resolve or unresolve an item.", "tags": [ - "Actions" + "Review queues" ], "security": [ { @@ -3738,6 +3043,109 @@ } ], "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The queue ID." + }, + "required": true, + "description": "The queue ID." + }, + { + "in": "query", + "name": "page", + "schema": { + "default": 1, + "description": "Page number, starting at 1.", + "type": "integer", + "minimum": 1, + "maximum": 100000 + } + }, + { + "in": "query", + "name": "pageSize", + "schema": { + "default": 20, + "description": "Number of items per page. Maximum 100.", + "type": "integer", + "minimum": 1, + "maximum": 100 + } + }, + { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "FIFO", + "LIFO", + "RANDOM", + "SEVERITY_DESC" + ] + } + }, + { + "in": "query", + "name": "includeResolved", + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "conversationIds", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "in": "query", + "name": "labels", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "in": "query", + "name": "authorId", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "filteredActionIds", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "in": "query", + "name": "afterDate", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "beforeDate", + "schema": { + "type": "string" + } + }, { "in": "header", "name": "Project-Id", @@ -3749,77 +3157,30 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "actionKey": { - "type": "string", - "description": "ID or key of the action to execute" - }, - "contentIds": { - "description": "IDs of the content items to apply the action to. Provide this or authorIds.", - "type": "array", - "items": { - "type": "string" - } - }, - "authorIds": { - "description": "IDs of the authors to apply the action to. Provide this or contentIds.", - "type": "array", - "items": { - "type": "string" - } - }, - "value": { - "description": "Optional value to provide with the action", - "type": "string" - }, - "queueId": { - "description": "Optional queue ID if the action is queue-specific", - "type": "string" - }, - "duration": { - "description": "Optional duration in milliseconds for actions with timeouts", - "type": "number", - "minimum": 0 - } - }, - "required": [ - "actionKey" - ], - "description": "Input parameters" - }, - "example": { - "actionKey": "suspend-24h", - "authorIds": [ - "user-1092" - ] - } - } - } - }, "responses": { "200": { - "description": "Action executed successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "success": { - "type": "boolean", - "description": "Whether the action was executed successfully" - } + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueueItem" + }, + "description": "The items on this page." + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } }, "required": [ - "success" + "data", + "pagination" ], - "additionalProperties": false, - "description": "Execution result" + "additionalProperties": false } } } @@ -3844,6 +3205,16 @@ } } }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3863,44 +3234,82 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "actions", - "x-speakeasy-name-override": "execute", + "x-speakeasy-group": "queue.items", + "x-speakeasy-name-override": "list", "x-speakeasy-mcp": { - "title": "Execute moderation action", + "title": "List queue items", "scopes": [ - "destructive" + "read" ], - "readOnlyHint": false, - "destructiveHint": true + "readOnlyHint": true, + "destructiveHint": false + }, + "x-speakeasy-pagination": { + "type": "offsetLimit", + "inputs": [ + { + "name": "page", + "in": "parameters", + "type": "page" + }, + { + "name": "pageSize", + "in": "parameters", + "type": "limit" + } + ], + "outputs": { + "results": "$.data", + "numPages": "$.pagination.totalPages" + } } } }, - "/actions/{actionId}/execute": { + "/queue/{id}/items/{itemId}/resolve": { "post": { - "operationId": "executeActionById", - "summary": "Execute an action", - "description": "Execute an action on a set of content items in a queue.", + "operationId": "resolveQueueItem", + "summary": "Resolve a queue item", + "description": "Mark an item of a queue as resolved. It is the same as a check-off in the dashboard: the item leaves the pending list and a QUEUE_ITEM_COMPLETED webhook is sent. To also take a moderation action, use `POST /actions/execute`.", "tags": [ - "Actions" + "Review queues" ], "security": [ { "Authorization": [] } ], - "deprecated": true, "parameters": [ { "in": "path", - "name": "actionId", + "name": "id", "schema": { "type": "string", - "description": "The ID or key of the action to execute." + "description": "The queue ID." }, "required": true, - "description": "The ID or key of the action to execute." + "description": "The queue ID." + }, + { + "in": "path", + "name": "itemId", + "schema": { + "type": "string", + "description": "The item ID to resolve." + }, + "required": true, + "description": "The item ID to resolve." }, { "in": "header", @@ -3913,68 +3322,25 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "contentIds": { - "description": "The IDs of the content items to perform the action on.", - "type": "array", - "items": { - "type": "string" - } - }, - "authorIds": { - "description": "IDs of the authors to apply the action to", - "type": "array", - "items": { - "type": "string" - } - }, - "queueId": { - "description": "The ID of the queue the action was performed from if any.", - "type": "string" - }, - "value": { - "description": "The value of the action. Useful to set a reason for the action etc.", - "type": "string" - } - } - } - } - } - }, "responses": { "200": { - "description": "Action executed successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { - "type": "boolean", - "description": "Action executed successfully." + "type": "boolean" }, - "actionId": { + "resolvedAt": { "type": "string", - "description": "The ID of the action." - }, - "ids": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The IDs of the content items." + "description": "When the item was resolved (ISO 8601)." } }, "required": [ "success", - "actionId", - "ids" + "resolvedAt" ], "additionalProperties": false } @@ -4001,12 +3367,12 @@ } } }, - "404": { - "description": "Not found", + "403": { + "description": "Insufficient access", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "$ref": "#/components/schemas/PermissionDeniedError" } } } @@ -4020,18 +3386,37 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-ignore": true + "x-speakeasy-group": "queue.items", + "x-speakeasy-name-override": "resolve", + "x-speakeasy-mcp": { + "title": "Resolve a queue item", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } } }, - "/webhooks": { - "get": { - "operationId": "listWebhooks", - "summary": "List webhooks", - "description": "List the webhooks of the project. The list is paginated.", + "/queue/{id}/items/resolve": { + "post": { + "operationId": "resolveQueueItems", + "summary": "Resolve queue items", + "description": "Mark up to 100 items of a queue as resolved in one request. Each item is resolved the same as with `POST /queue/{id}/items/{itemId}/resolve`. IDs that do not exist in the project are skipped and returned in `notFoundIds`.", "tags": [ - "Webhooks" + "Review queues" ], "security": [ { @@ -4040,26 +3425,14 @@ ], "parameters": [ { - "in": "query", - "name": "page", - "schema": { - "default": 1, - "description": "Page number, starting at 1.", - "type": "integer", - "minimum": 1, - "maximum": 100000 - } - }, - { - "in": "query", - "name": "pageSize", + "in": "path", + "name": "id", "schema": { - "default": 20, - "description": "Number of items per page. Maximum 100.", - "type": "integer", - "minimum": 1, - "maximum": 100 - } + "type": "string", + "description": "The queue ID." + }, + "required": true, + "description": "The queue ID." }, { "in": "header", @@ -4072,97 +3445,75 @@ "x-speakeasy-name-override": "projectId" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "itemIds": { + "minItems": 1, + "maxItems": 100, + "type": "array", + "items": { + "type": "string" + }, + "description": "The item IDs to resolve. Up to 100." + } + }, + "required": [ + "itemIds" + ] + }, + "example": { + "itemIds": [ + "clx8k2v0e000108l4a9bz3f7q", + "clx8k2v0e000208l4h2mc6d1r" + ] + } + } + } + }, "responses": { "200": { - "description": "Webhooks retrieved successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "data": { + "resolved": { + "type": "integer", + "description": "Number of items resolved." + }, + "notFoundIds": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the webhook." - }, - "name": { - "type": "string", - "description": "The webhook's name." - }, - "description": { - "description": "The webhook's description.", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "description": "The URL we call when a subscribed event occurs." - }, - "eventTypes": { - "default": [], - "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", - "type": "array", - "items": { - "type": "string", - "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" - ] - } - }, - "payloadVersion": { - "type": "string", - "enum": [ - "V1", - "V2" - ], - "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." - }, - "createdAt": { - "description": "The date the webhook was created.", - "type": "string" - } - }, - "required": [ - "id", - "name", - "url", - "eventTypes", - "payloadVersion", - "createdAt" - ], - "additionalProperties": false + "type": "string" }, - "description": "The items on this page." - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" + "description": "Item IDs that do not exist in the project." } }, "required": [ - "data", - "pagination" + "resolved", + "notFoundIds" ], "additionalProperties": false } } } }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "401": { "description": "Authorization not provided", "content": { @@ -4173,6 +3524,16 @@ } } }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, "429": { "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { @@ -4182,44 +3543,37 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "webhooks", - "x-speakeasy-name-override": "list", + "x-speakeasy-group": "queue.items", + "x-speakeasy-name-override": "resolveMany", "x-speakeasy-mcp": { - "title": "List webhooks", + "title": "Resolve queue items", "scopes": [ - "read" + "write" ], - "readOnlyHint": true, + "readOnlyHint": false, "destructiveHint": false - }, - "x-speakeasy-pagination": { - "type": "offsetLimit", - "inputs": [ - { - "name": "page", - "in": "parameters", - "type": "page" - }, - { - "name": "pageSize", - "in": "parameters", - "type": "limit" - } - ], - "outputs": { - "results": "$.data", - "numPages": "$.pagination.totalPages" - } } - }, + } + }, + "/queue/{id}/items/{itemId}/unresolve": { "post": { - "operationId": "createWebhook", - "summary": "Create a webhook", - "description": "Create a webhook subscribed to one or more event types. Deliveries use the v2 envelope and are signed with the project signing secret (see the signing secret endpoint).", + "operationId": "unresolveQueueItem", + "summary": "Unresolve a queue item", + "description": "Mark a resolved item of a queue as pending again. It is the same as removing a check-off in the dashboard: the item comes back in the pending list.", "tags": [ - "Webhooks" + "Review queues" ], "security": [ { @@ -4227,6 +3581,26 @@ } ], "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The queue ID." + }, + "required": true, + "description": "The queue ID." + }, + { + "in": "path", + "name": "itemId", + "schema": { + "type": "string", + "description": "The item ID to unresolve." + }, + "required": true, + "description": "The item ID to unresolve." + }, { "in": "header", "name": "Project-Id", @@ -4238,137 +3612,34 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "description": "The webhook's name, used to identify it in the dashboard" - }, - "description": { - "description": "The webhook's description", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "format": "uri", - "description": "The webhook's URL. We'll call this URL when an event occurs." - }, - "eventTypes": { - "minItems": 1, - "type": "array", - "items": { - "type": "string", - "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" - ] - }, - "description": "Event types this webhook subscribes to. One webhook URL receives all events you list here." - } - }, - "required": [ - "name", - "url", - "eventTypes" - ] - }, - "example": { - "name": "Moderation events", - "url": "https://example.com/webhooks/moderation", - "eventTypes": [ - "QUEUE_ITEM_REJECTED", - "AUTHOR_BLOCKED" - ] - } - } - } - }, "responses": { "200": { - "description": "Webhook created successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "The ID of the webhook." - }, - "name": { - "type": "string", - "description": "The webhook's name." - }, - "description": { - "description": "The webhook's description.", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "description": "The URL we call when a subscribed event occurs." - }, - "eventTypes": { - "default": [], - "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", - "type": "array", - "items": { - "type": "string", - "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" - ] - } + "success": { + "type": "boolean" }, - "payloadVersion": { + "status": { "type": "string", "enum": [ - "V1", - "V2" + "pending", + "resolved" ], - "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." + "description": "`pending` waits for review. `resolved` is reviewed." }, - "createdAt": { - "description": "The date the webhook was created.", - "type": "string" + "unresolvedAt": { + "type": "string", + "description": "When the item was unresolved (ISO 8601)." } }, "required": [ - "id", - "name", - "url", - "eventTypes", - "payloadVersion", - "createdAt" + "success", + "status", + "unresolvedAt" ], "additionalProperties": false } @@ -4395,186 +3666,56 @@ } } }, - "429": { - "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "403": { + "description": "Insufficient access", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RateLimitError" - } - } - } - } - }, - "x-speakeasy-group": "webhooks", - "x-speakeasy-name-override": "create", - "x-speakeasy-mcp": { - "title": "Create a webhook", - "scopes": [ - "write" - ], - "readOnlyHint": false, - "destructiveHint": false - } - } - }, - "/webhooks/{id}": { - "get": { - "operationId": "getWebhook", - "summary": "Get a webhook", - "description": "Get a webhook by ID.", - "tags": [ - "Webhooks" - ], - "security": [ - { - "Authorization": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "description": "The ID of the webhook to get." - }, - "required": true, - "description": "The ID of the webhook to get." - }, - { - "in": "header", - "name": "Project-Id", - "required": false, - "schema": { - "type": "string" - }, - "description": "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.", - "x-speakeasy-name-override": "projectId" - } - ], - "responses": { - "200": { - "description": "Webhook retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the webhook." - }, - "name": { - "type": "string", - "description": "The webhook's name." - }, - "description": { - "description": "The webhook's description.", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "description": "The URL we call when a subscribed event occurs." - }, - "eventTypes": { - "default": [], - "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", - "type": "array", - "items": { - "type": "string", - "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" - ] - } - }, - "payloadVersion": { - "type": "string", - "enum": [ - "V1", - "V2" - ], - "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." - }, - "createdAt": { - "description": "The date the webhook was created.", - "type": "string" - } - }, - "required": [ - "id", - "name", - "url", - "eventTypes", - "payloadVersion", - "createdAt" - ], - "additionalProperties": false - } - } - } - }, - "401": { - "description": "Authorization not provided", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticationError" + "$ref": "#/components/schemas/PermissionDeniedError" } } } }, - "404": { - "description": "Not found", + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "$ref": "#/components/schemas/RateLimitError" } } } }, - "429": { - "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "500": { + "description": "Internal server error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RateLimitError" + "$ref": "#/components/schemas/InternalServerError" } } } } }, - "x-speakeasy-group": "webhooks", - "x-speakeasy-name-override": "retrieve", + "x-speakeasy-group": "queue.items", + "x-speakeasy-name-override": "unresolve", "x-speakeasy-mcp": { - "title": "Get a webhook", + "title": "Unresolve a queue item", "scopes": [ - "read" + "write" ], - "readOnlyHint": true, + "readOnlyHint": false, "destructiveHint": false } - }, - "put": { - "operationId": "updateWebhook", - "summary": "Update a webhook", - "description": "Update a webhook. Legacy v1 webhooks are read-only: delete them and create a new webhook instead.", + } + }, + "/actions": { + "get": { + "operationId": "listActions", + "summary": "List moderation actions", + "description": "List the moderation actions of the project. The list is paginated.", "tags": [ - "Webhooks" + "Actions" ], "security": [ { @@ -4583,14 +3724,33 @@ ], "parameters": [ { - "in": "path", - "name": "id", + "in": "query", + "name": "queueId", "schema": { - "type": "string", - "description": "The ID of the webhook to update." - }, - "required": true, - "description": "The ID of the webhook to update." + "type": "string" + } + }, + { + "in": "query", + "name": "page", + "schema": { + "default": 1, + "description": "Page number, starting at 1.", + "type": "integer", + "minimum": 1, + "maximum": 100000 + } + }, + { + "in": "query", + "name": "pageSize", + "schema": { + "default": 20, + "description": "Number of items per page. Maximum 100.", + "type": "integer", + "minimum": 1, + "maximum": 100 + } }, { "in": "header", @@ -4603,124 +3763,156 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "description": "The webhook's name, used to identify it in the dashboard" - }, - "description": { - "description": "The webhook's description", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "format": "uri", - "description": "The webhook's URL. We'll call this URL when an event occurs." - }, - "eventTypes": { - "minItems": 1, - "type": "array", - "items": { - "type": "string", - "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" - ] - }, - "description": "Event types this webhook subscribes to. One webhook URL receives all events you list here." - } - } - } - } - } - }, "responses": { "200": { - "description": "Webhook updated successfully", + "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "The ID of the webhook." - }, - "name": { - "type": "string", - "description": "The webhook's name." - }, - "description": { - "description": "The webhook's description.", - "type": [ - "string", - "null" - ] - }, - "url": { - "type": "string", - "description": "The URL we call when a subscribed event occurs." - }, - "eventTypes": { - "default": [], - "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", + "data": { "type": "array", "items": { - "type": "string", - "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" - ] - } - }, - "payloadVersion": { - "type": "string", - "enum": [ - "V1", - "V2" - ], - "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the action." + }, + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "description": "The date the action was created.", + "type": "string" + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the action.", + "anyOf": [ + { + "type": "string", + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] + }, + { + "type": "null" + } + ] + }, + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] + }, + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "type": "string", + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } + }, + "required": [ + "value" + ], + "additionalProperties": false + } + }, + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" + } + }, + "required": [ + "id", + "createdAt", + "name", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText" + ], + "additionalProperties": false + }, + "description": "The items on this page." }, - "createdAt": { - "description": "The date the webhook was created.", - "type": "string" + "pagination": { + "$ref": "#/components/schemas/Pagination" } }, "required": [ - "id", - "name", - "url", - "eventTypes", - "payloadVersion", - "createdAt" + "data", + "pagination" ], "additionalProperties": false } @@ -4747,6 +3939,16 @@ } } }, + "403": { + "description": "Insufficient access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionDeniedError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -4766,25 +3968,54 @@ } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } } }, - "x-speakeasy-group": "webhooks", - "x-speakeasy-name-override": "update", + "x-speakeasy-group": "actions", + "x-speakeasy-name-override": "list", "x-speakeasy-mcp": { - "title": "Update a webhook", + "title": "List moderation actions", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false + }, + "x-speakeasy-pagination": { + "type": "offsetLimit", + "inputs": [ + { + "name": "page", + "in": "parameters", + "type": "page" + }, + { + "name": "pageSize", + "in": "parameters", + "type": "limit" + } + ], + "outputs": { + "results": "$.data", + "numPages": "$.pagination.totalPages" + } } }, - "delete": { - "operationId": "deleteWebhook", - "summary": "Delete a webhook", - "description": "Delete a webhook.", + "post": { + "operationId": "createAction", + "summary": "Create an action", + "description": "Create an action.", "tags": [ - "Webhooks" + "Actions" ], "security": [ { @@ -4792,16 +4023,6 @@ } ], "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "description": "The ID of the webhook to delete." - }, - "required": true, - "description": "The ID of the webhook to delete." - }, { "in": "header", "name": "Project-Id", @@ -4813,33 +4034,281 @@ "x-speakeasy-name-override": "projectId" } ], - "responses": { + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the action.", + "anyOf": [ + { + "type": "string", + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] + }, + { + "type": "null" + } + ] + }, + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] + }, + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "type": "string", + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } + }, + "required": [ + "value" + ] + } + }, + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" + } + }, + "required": [ + "name" + ] + }, + "example": { + "name": "Suspend for 24 hours", + "key": "suspend-24h", + "type": "AUTHOR_BLOCK_TEMP", + "queueBehaviour": "REMOVE" + } + } + } + }, + "responses": { "200": { - "description": "Webhook deleted successfully", + "description": "Action created successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "deleted": { - "type": "boolean", - "description": "Whether the webhook was deleted." - }, "id": { "type": "string", - "description": "The ID of the webhook." + "description": "The ID of the action." + }, + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "description": "The date the action was created.", + "type": "string" + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the action.", + "anyOf": [ + { + "type": "string", + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] + }, + { + "type": "null" + } + ] + }, + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] + }, + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "type": "string", + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } + }, + "required": [ + "value" + ], + "additionalProperties": false + } + }, + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" } }, "required": [ - "deleted", - "id" + "id", + "createdAt", + "name", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText" ], "additionalProperties": false } } } }, - "401": { + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { "description": "Authorization not provided", "content": { "application/json": { @@ -4849,12 +4318,12 @@ } } }, - "404": { - "description": "Not found", + "409": { + "description": "Action with this key already exists", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "$ref": "#/components/schemas/ConflictError" } } } @@ -4870,25 +4339,25 @@ } } }, - "x-speakeasy-group": "webhooks", - "x-speakeasy-name-override": "delete", + "x-speakeasy-group": "actions", + "x-speakeasy-name-override": "create", "x-speakeasy-mcp": { - "title": "Delete a webhook", + "title": "Create an action", "scopes": [ - "destructive" + "write" ], "readOnlyHint": false, - "destructiveHint": true + "destructiveHint": false } } }, - "/webhook-secret": { + "/actions/{id}": { "get": { - "operationId": "getWebhookSecret", - "summary": "Get the webhook signing secret", - "description": "Get the signing secret used to sign webhook deliveries for this project, creating one if none exists yet. Verify deliveries by comparing the `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded.", + "operationId": "getAction", + "summary": "Get an action", + "description": "Get an action by ID.", "tags": [ - "Webhooks" + "Actions" ], "security": [ { @@ -4896,6 +4365,16 @@ } ], "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The ID of the action to get." + }, + "required": true, + "description": "The ID of the action to get." + }, { "in": "header", "name": "Project-Id", @@ -4909,25 +4388,153 @@ ], "responses": { "200": { - "description": "Signing secret retrieved successfully", + "description": "Action retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "secret": { + "id": { "type": "string", - "description": "The signing secret for this project. Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, hex-encoded in the `modapi-signature` header." + "description": "The ID of the action." + }, + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "description": "The date the action was created.", + "type": "string" + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the action.", + "anyOf": [ + { + "type": "string", + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] + }, + { + "type": "null" + } + ] + }, + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] + }, + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "type": "string", + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } + }, + "required": [ + "value" + ], + "additionalProperties": false + } + }, + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" } }, "required": [ - "secret" + "id", + "createdAt", + "name", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText" ], "additionalProperties": false } } } }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "401": { "description": "Authorization not provided", "content": { @@ -4938,6 +4545,16 @@ } } }, + "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": { @@ -4949,28 +4566,40 @@ } } }, - "x-speakeasy-group": "webhookSecret", + "x-speakeasy-group": "actions", "x-speakeasy-name-override": "retrieve", "x-speakeasy-mcp": { - "disabled": true + "title": "Get an action", + "scopes": [ + "read" + ], + "readOnlyHint": true, + "destructiveHint": false } - } - }, - "/moderate/text": { - "post": { - "operationId": "moderateText", - "summary": "Analyze text", - "description": "(Deprecated \u2014 use https://api.moderationapi.com/v1/moderate) Analyze text content. Runs the same policy engine as POST /moderate; the per-model result blocks are approximated from the policies it ran.", + }, + "put": { + "operationId": "updateAction", + "summary": "Update an action", + "description": "Update an action.", "tags": [ - "Content" + "Actions" ], "security": [ { "Authorization": [] } ], - "deprecated": true, "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The ID of the action to update." + }, + "required": true, + "description": "The ID of the action to update." + }, { "in": "header", "name": "Project-Id", @@ -4989,219 +4618,250 @@ "schema": { "type": "object", "properties": { - "contentId": { - "description": "The unique ID of the content in your database.", - "type": "string", - "minLength": 1, - "maxLength": 100 + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] }, - "channelKey": { - "description": "The key of the channel.", - "type": "string" + "name": { + "type": "string", + "description": "The name of the action." }, - "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" + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] }, - "authorId": { - "description": "The author of the content.", - "type": "string" + "type": { + "description": "The type of the action.", + "anyOf": [ + { + "type": "string", + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] + }, + { + "type": "null" + } + ] }, - "contextId": { - "description": "For example the ID of a chat room or a post", - "type": "string" + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] }, - "metadata": { - "description": "Any metadata you want to store with the content", - "type": "object", - "propertyNames": { + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { "type": "string" - }, - "additionalProperties": {} + } }, - "value": { + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", "type": "string", - "maxLength": 100000, - "description": "The text you'd like to analyze. We recommend to submit plain text or HTML" + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } + }, + "required": [ + "value" + ] + } + }, + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" } - }, - "required": [ - "value" - ], - "description": "Input parameters for text moderation" + } } } } }, "responses": { "200": { - "description": "Successful response", + "description": "Action updated successfully", "content": { "application/json": { "schema": { - "allOf": [ - { - "type": "object", - "properties": { - "status": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the action." + }, + "key": { + "description": "User defined key of the action.", + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "description": "The date the action was created.", + "type": "string" + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "description": { + "description": "The description of the action.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The type of the action.", + "anyOf": [ + { "type": "string", - "description": "Success if the request was successful" - }, - "contentId": { - "description": "The ID of the content.", - "type": "string" - }, - "request": { - "type": "object", - "properties": { - "timestamp": { - "type": "number", - "description": "The timestamp of the request" - }, - "quota_usage": { - "type": "number", - "description": "The quota usage of the request" - } - }, - "required": [ - "timestamp", - "quota_usage" - ], - "additionalProperties": false, - "description": "Information about the request" + "enum": [ + "AUTHOR_BLOCK", + "AUTHOR_BLOCK_TEMP", + "AUTHOR_UNBLOCK", + "AUTHOR_DELETE", + "AUTHOR_REPORT", + "AUTHOR_WARN", + "AUTHOR_CUSTOM", + "ITEM_REJECT", + "ITEM_ALLOW", + "ITEM_CUSTOM" + ] }, - "flagged": { - "type": "boolean", - "description": "Whether the content was flagged by any models" + { + "type": "null" + } + ] + }, + "builtIn": { + "default": false, + "description": "Whether the action is a built-in action or a custom one.", + "type": [ + "boolean", + "null" + ] + }, + "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.", + "type": "string", + "enum": [ + "REMOVE", + "ADD", + "NO_CHANGE" + ] + }, + "filterInQueueIds": { + "default": [], + "description": "The IDs of the queues the action is available in.", + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "default": "ALL_QUEUES", + "description": "Show the action in all queues, selected queues or no queues (to use via API only).", + "type": "string", + "enum": [ + "ALL_QUEUES", + "SOME_QUEUES", + "HIDDEN" + ] + }, + "possibleValues": { + "default": [], + "description": "The possible values of the action. The user will be prompted to select one of these values when executing the action.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value of the action." + } }, - "author": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Author ID in Moderation API" - }, - "external_id": { - "description": "The author's ID from your system", - "type": [ - "string", - "null" - ] - }, - "block": { - "anyOf": [ - { - "type": "object", - "properties": { - "until": { - "description": "The timestamp until which they are blocked if the author is suspended.", - "type": [ - "number", - "null" - ] - }, - "reason": { - "description": "The moderators reason why the author was blocked or suspended.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "Block or suspension details, if applicable. Null if the author is enabled." - }, - "status": { - "type": "string", - "enum": [ - "enabled", - "suspended", - "blocked" - ], - "description": "Current author status" - }, - "trust_level": { - "type": "object", - "properties": { - "level": { - "type": "number", - "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" - }, - "manual": { - "type": "boolean", - "description": "True if the trust level was set manually by a moderator" - } - }, - "required": [ - "level", - "manual" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "block", - "status", - "trust_level" - ], - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "The author of the content if your account has authors enabled. Requires you to send authorId when submitting content." - }, - "content_moderated": { - "type": "boolean", - "description": "Whether the content was moderated or not. Same as `content` !== `original`" - }, - "unicode_spoofing": { - "type": "boolean", - "description": "Whether the content is using look-alike characters. Often used by spammers." - }, - "data_found": { - "type": "boolean", - "description": "Whether any entity matchers found data for the content" - }, - "original": { - "type": "string", - "description": "The original content" - }, - "content": { - "type": "string", - "description": "The content after moderation. With all mask replacements applied and look-alike characters replaced with the original characters." - } - }, - "required": [ - "status", - "request", - "flagged", - "author", - "content_moderated", - "unicode_spoofing", - "data_found", - "original", - "content" - ], - "additionalProperties": false + "required": [ + "value" + ], + "additionalProperties": false + } }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} + "valueRequired": { + "default": false, + "description": "Whether the action requires a value to be executed.", + "type": "boolean" + }, + "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" } - ] + }, + "required": [ + "id", + "createdAt", + "name", + "builtIn", + "queueBehaviour", + "filterInQueueIds", + "position", + "possibleValues", + "valueRequired", + "freeText" + ], + "additionalProperties": false } } } @@ -5226,55 +4886,71 @@ } } }, - "403": { - "description": "Insufficient access", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" + "$ref": "#/components/schemas/NotFoundError" } } } }, - "429": { - "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "409": { + "description": "Action with this key already exists", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RateLimitError" + "$ref": "#/components/schemas/ConflictError" } } } }, - "500": { - "description": "Internal server error", + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InternalServerError" + "$ref": "#/components/schemas/RateLimitError" } } } } }, - "x-speakeasy-ignore": true - } - }, - "/moderate/image": { - "post": { - "operationId": "moderateImage", - "summary": "Analyze image", - "description": "(Deprecated \u2014 use https://api.moderationapi.com/v1/moderate) Analyze an image. Runs the same policy engine as POST /moderate; `labels` is approximated from the policies it ran and `texts` is no longer populated.", + "x-speakeasy-group": "actions", + "x-speakeasy-name-override": "update", + "x-speakeasy-mcp": { + "title": "Update an action", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + }, + "delete": { + "operationId": "deleteAction", + "summary": "Delete an action", + "description": "Delete an action and all of its webhooks.", "tags": [ - "Content" + "Actions" ], "security": [ { "Authorization": [] } ], - "deprecated": true, "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The ID of the action to delete." + }, + "required": true, + "description": "The ID of the action to delete." + }, { "in": "header", "name": "Project-Id", @@ -5286,233 +4962,34 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "contentId": { - "description": "The unique ID of the content in your database.", - "type": "string", - "minLength": 1, - "maxLength": 100 - }, - "channelKey": { - "description": "The key of the channel.", - "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" - }, - "authorId": { - "description": "The author of the content.", - "type": "string" - }, - "contextId": { - "description": "For example the ID of a chat room or a post", - "type": "string" - }, - "metadata": { - "description": "Any metadata you want to store with the content", - "type": "object", - "propertyNames": { - "type": "string" + "responses": { + "200": { + "description": "Action deleted successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "description": "Whether the action was deleted." }, - "additionalProperties": {} + "id": { + "type": "string", + "description": "The ID of the action." + } }, - "url": { - "type": "string", - "format": "uri", - "description": "The URL of the image you want to analyze." - } - }, - "required": [ - "url" - ], - "description": "Input parameters for image moderation" + "required": [ + "deleted", + "id" + ], + "additionalProperties": false + } } } - } - }, - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "type": "object", - "properties": { - "status": { - "type": "string", - "description": "Success if the request was successful" - }, - "contentId": { - "description": "The ID of the content.", - "type": "string" - }, - "request": { - "type": "object", - "properties": { - "timestamp": { - "type": "number", - "description": "The timestamp of the request" - }, - "quota_usage": { - "type": "number", - "description": "The quota usage of the request" - } - }, - "required": [ - "timestamp", - "quota_usage" - ], - "additionalProperties": false, - "description": "Information about the request" - }, - "flagged": { - "type": "boolean", - "description": "Whether the content was flagged by any models" - }, - "author": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Author ID in Moderation API" - }, - "external_id": { - "description": "The author's ID from your system", - "type": [ - "string", - "null" - ] - }, - "block": { - "anyOf": [ - { - "type": "object", - "properties": { - "until": { - "description": "The timestamp until which they are blocked if the author is suspended.", - "type": [ - "number", - "null" - ] - }, - "reason": { - "description": "The moderators reason why the author was blocked or suspended.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "Block or suspension details, if applicable. Null if the author is enabled." - }, - "status": { - "type": "string", - "enum": [ - "enabled", - "suspended", - "blocked" - ], - "description": "Current author status" - }, - "trust_level": { - "type": "object", - "properties": { - "level": { - "type": "number", - "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" - }, - "manual": { - "type": "boolean", - "description": "True if the trust level was set manually by a moderator" - } - }, - "required": [ - "level", - "manual" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "block", - "status", - "trust_level" - ], - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "The author of the content if your account has authors enabled. Requires you to send authorId when submitting content." - }, - "data_found": { - "type": "boolean", - "description": "Whether any entity matchers found data for the content" - }, - "labels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "score": { - "type": "number" - } - }, - "required": [ - "label", - "score" - ], - "additionalProperties": false - }, - "description": "Legacy image labels, approximated from the equivalent policies. Use POST /moderate for the policy probabilities themselves." - } - }, - "required": [ - "status", - "request", - "flagged", - "author", - "data_found", - "labels" - ], - "additionalProperties": false - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - ] - } - } - } - }, - "400": { - "description": "Invalid input data", + }, + "400": { + "description": "Invalid input data", "content": { "application/json": { "schema": { @@ -5531,12 +5008,12 @@ } } }, - "403": { - "description": "Insufficient access", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" + "$ref": "#/components/schemas/NotFoundError" } } } @@ -5550,28 +5027,27 @@ } } } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InternalServerError" - } - } - } } }, - "x-speakeasy-ignore": true + "x-speakeasy-group": "actions", + "x-speakeasy-name-override": "delete", + "x-speakeasy-mcp": { + "title": "Delete an action", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } } }, - "/account": { - "get": { - "operationId": "getAccount", - "summary": "Get account details", - "description": "Get account details", + "/actions/execute": { + "post": { + "operationId": "executeAction", + "summary": "Execute moderation action", + "description": "Execute a moderation action on one or more content items.", "tags": [ - "Account" + "Actions" ], "security": [ { @@ -5590,139 +5066,77 @@ "x-speakeasy-name-override": "projectId" } ], - "responses": { - "200": { - "description": "Account details retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the account" - }, - "paid_plan_name": { - "type": "string", - "description": "Name of the paid plan" - }, - "text_api_quota": { - "type": "number", - "description": "Text API quota" - }, - "remaining_quota": { - "type": "number", - "description": "Remaining quota" - }, - "current_project": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the current project" - }, - "name": { - "type": "string", - "description": "Name of the current project" - } - }, - "required": [ - "id", - "name" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "paid_plan_name", - "text_api_quota", - "remaining_quota" - ], - "additionalProperties": false - } - } - } - }, - "401": { - "description": "Authorization not provided", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticationError" - } - } - } - }, - "429": { - "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RateLimitError" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "actionKey": { + "type": "string", + "description": "ID or key of the action to execute" + }, + "contentIds": { + "description": "IDs of the content items to apply the action to. Provide this or authorIds.", + "type": "array", + "items": { + "type": "string" + } + }, + "authorIds": { + "description": "IDs of the authors to apply the action to. Provide this or contentIds.", + "type": "array", + "items": { + "type": "string" + } + }, + "value": { + "description": "Optional value to provide with the action", + "type": "string" + }, + "queueId": { + "description": "Optional queue ID if the action is queue-specific", + "type": "string" + }, + "duration": { + "description": "Optional duration in milliseconds for actions with timeouts", + "type": "number", + "minimum": 0 + } + }, + "required": [ + "actionKey" + ], + "description": "Input parameters" + }, + "example": { + "actionKey": "suspend-24h", + "authorIds": [ + "user-1092" + ] } } } }, - "x-speakeasy-group": "account", - "x-speakeasy-name-override": "retrieve", - "x-speakeasy-mcp": { - "title": "Get account details", - "scopes": [ - "read" - ], - "readOnlyHint": true, - "destructiveHint": false - } - } - }, - "/auth": { - "get": { - "operationId": "getAuth", - "tags": [ - "Account" - ], - "security": [ - { - "Authorization": [] - } - ], - "deprecated": true, - "parameters": [ - { - "in": "header", - "name": "Project-Id", - "required": false, - "schema": { - "type": "string" - }, - "description": "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.", - "x-speakeasy-name-override": "projectId" - } - ], "responses": { "200": { - "description": "Successful response", + "description": "Action executed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "status": { - "type": "string", - "description": "Status of the authentication" - }, - "message": { - "type": "string", - "description": "Message of the authentication" + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" } }, "required": [ - "status", - "message" + "success" ], - "additionalProperties": false + "additionalProperties": false, + "description": "Execution result" } } } @@ -5747,16 +5161,6 @@ } } }, - "403": { - "description": "Insufficient access", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -5767,23 +5171,36 @@ } } }, - "500": { - "description": "Internal server error", + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InternalServerError" + "$ref": "#/components/schemas/RateLimitError" } } } } }, - "x-speakeasy-ignore": true - }, + "x-speakeasy-group": "actions", + "x-speakeasy-name-override": "execute", + "x-speakeasy-mcp": { + "title": "Execute moderation action", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/actions/{actionId}/execute": { "post": { - "operationId": "createAuth", + "operationId": "executeActionById", + "summary": "Execute an action", + "description": "Execute an action on a set of content items in a queue.", "tags": [ - "Account" + "Actions" ], "security": [ { @@ -5792,6 +5209,16 @@ ], "deprecated": true, "parameters": [ + { + "in": "path", + "name": "actionId", + "schema": { + "type": "string", + "description": "The ID or key of the action to execute." + }, + "required": true, + "description": "The ID or key of the action to execute." + }, { "in": "header", "name": "Project-Id", @@ -5809,36 +5236,62 @@ "application/json": { "schema": { "type": "object", - "properties": {} + "properties": { + "contentIds": { + "description": "The IDs of the content items to perform the action on.", + "type": "array", + "items": { + "type": "string" + } + }, + "authorIds": { + "description": "IDs of the authors to apply the action to", + "type": "array", + "items": { + "type": "string" + } + }, + "queueId": { + "description": "The ID of the queue the action was performed from if any.", + "type": "string" + }, + "value": { + "description": "The value of the action. Useful to set a reason for the action etc.", + "type": "string" + } + } } } } }, "responses": { "200": { - "description": "Successful response", + "description": "Action executed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "status": { - "type": "string", - "description": "Status of the authentication" + "success": { + "type": "boolean", + "description": "Action executed successfully." }, - "message": { + "actionId": { "type": "string", - "description": "Message of the authentication" + "description": "The ID of the action." }, - "project": { - "type": "string", - "description": "Name of the authenticated project" + "ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The IDs of the content items." } }, "required": [ - "status", - "message", - "project" + "success", + "actionId", + "ids" ], "additionalProperties": false } @@ -5865,22 +5318,22 @@ } } }, - "403": { - "description": "Insufficient access", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" + "$ref": "#/components/schemas/NotFoundError" } } } }, - "500": { - "description": "Internal server error", + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InternalServerError" + "$ref": "#/components/schemas/RateLimitError" } } } @@ -5889,13 +5342,13 @@ "x-speakeasy-ignore": true } }, - "/wordlist": { + "/webhooks": { "get": { - "operationId": "listWordlists", - "summary": "List wordlists", - "description": "List the wordlists of the organization. The list is paginated.", + "operationId": "listWebhooks", + "summary": "List webhooks", + "description": "List the webhooks of the project. The list is paginated.", "tags": [ - "Wordlists" + "Webhooks" ], "security": [ { @@ -5938,7 +5391,7 @@ ], "responses": { "200": { - "description": "Successful response", + "description": "Webhooks retrieved successfully", "content": { "application/json": { "schema": { @@ -5951,43 +5404,64 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier of the wordlist" + "description": "The ID of the webhook." }, "name": { - "description": "Name of the wordlist", - "type": [ - "string", - "null" - ] + "type": "string", + "description": "The webhook's name." }, "description": { - "description": "Description of the wordlist", + "description": "The webhook's description.", "type": [ "string", "null" ] }, - "createdAt": { - "description": "When the wordlist was created", - "type": [ - "string", - "null" - ] + "url": { + "type": "string", + "description": "The URL we call when a subscribed event occurs." }, - "userId": { - "description": "User who created the wordlist", - "type": [ - "string", - "null" - ] + "eventTypes": { + "default": [], + "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", + "type": "array", + "items": { + "type": "string", + "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" + ] + } + }, + "payloadVersion": { + "type": "string", + "enum": [ + "V1", + "V2" + ], + "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." + }, + "createdAt": { + "description": "The date the webhook was created.", + "type": "string" } }, "required": [ "id", "name", - "description", - "createdAt", - "userId" + "url", + "eventTypes", + "payloadVersion", + "createdAt" ], "additionalProperties": false }, @@ -6006,16 +5480,6 @@ } } }, - "400": { - "description": "Invalid input data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "401": { "description": "Authorization not provided", "content": { @@ -6026,26 +5490,6 @@ } } }, - "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": { @@ -6055,22 +5499,12 @@ } } } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InternalServerError" - } - } - } } }, - "x-speakeasy-group": "wordlist", + "x-speakeasy-group": "webhooks", "x-speakeasy-name-override": "list", "x-speakeasy-mcp": { - "title": "List wordlists", + "title": "List webhooks", "scopes": [ "read" ], @@ -6096,15 +5530,13 @@ "numPages": "$.pagination.totalPages" } } - } - }, - "/wordlist/{id}": { - "get": { - "operationId": "getWordlist", - "summary": "Get wordlist", - "description": "Get a specific wordlist by ID", + }, + "post": { + "operationId": "createWebhook", + "summary": "Create a webhook", + "description": "Create a webhook subscribed to one or more event types. Deliveries use the v2 envelope and are signed with the project signing secret (see the signing secret endpoint).", "tags": [ - "Wordlists" + "Webhooks" ], "security": [ { @@ -6112,16 +5544,6 @@ } ], "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "description": "ID of the wordlist to get" - }, - "required": true, - "description": "ID of the wordlist to get" - }, { "in": "header", "name": "Project-Id", @@ -6133,9 +5555,72 @@ "x-speakeasy-name-override": "projectId" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "The webhook's name, used to identify it in the dashboard" + }, + "description": { + "description": "The webhook's description", + "type": [ + "string", + "null" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "The webhook's URL. We'll call this URL when an event occurs." + }, + "eventTypes": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "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" + ] + }, + "description": "Event types this webhook subscribes to. One webhook URL receives all events you list here." + } + }, + "required": [ + "name", + "url", + "eventTypes" + ] + }, + "example": { + "name": "Moderation events", + "url": "https://example.com/webhooks/moderation", + "eventTypes": [ + "QUEUE_ITEM_REJECTED", + "AUTHOR_BLOCKED" + ] + } + } + } + }, "responses": { "200": { - "description": "Successful response", + "description": "Webhook created successfully", "content": { "application/json": { "schema": { @@ -6143,58 +5628,64 @@ "properties": { "id": { "type": "string", - "description": "ID of the wordlist" + "description": "The ID of the webhook." }, "name": { - "anyOf": [ - { - "type": "string", - "description": "Name of the wordlist" - }, - { - "type": "null" - } + "type": "string", + "description": "The webhook's name." + }, + "description": { + "description": "The webhook's description.", + "type": [ + "string", + "null" ] }, - "words": { + "url": { + "type": "string", + "description": "The URL we call when a subscribed event occurs." + }, + "eventTypes": { + "default": [], + "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", "type": "array", "items": { - "type": "string" - }, - "description": "Words in the wordlist" - }, - "createdAt": { - "description": "Creation date of the wordlist", - "type": "string" + "type": "string", + "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" + ] + } }, - "organizationId": { + "payloadVersion": { "type": "string", - "description": "ID of the organization" + "enum": [ + "V1", + "V2" + ], + "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." }, - "userId": { - "anyOf": [ - { - "type": "string", - "description": "ID of the user" - }, - { - "type": "null" - } - ] - }, - "strict": { - "type": "boolean", - "description": "Strict mode" + "createdAt": { + "description": "The date the webhook was created.", + "type": "string" } }, "required": [ "id", "name", - "words", - "createdAt", - "organizationId", - "userId", - "strict" + "url", + "eventTypes", + "payloadVersion", + "createdAt" ], "additionalProperties": false } @@ -6221,26 +5712,6 @@ } } }, - "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": { @@ -6250,35 +5721,27 @@ } } } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InternalServerError" - } - } - } } }, - "x-speakeasy-group": "wordlist", - "x-speakeasy-name-override": "retrieve", + "x-speakeasy-group": "webhooks", + "x-speakeasy-name-override": "create", "x-speakeasy-mcp": { - "title": "Get wordlist", + "title": "Create a webhook", "scopes": [ - "read" + "write" ], - "readOnlyHint": true, + "readOnlyHint": false, "destructiveHint": false } - }, - "put": { - "operationId": "updateWordlist", - "summary": "Update wordlist", - "description": "Update a wordlist", + } + }, + "/webhooks/{id}": { + "get": { + "operationId": "getWebhook", + "summary": "Get a webhook", + "description": "Get a webhook by ID.", "tags": [ - "Wordlists" + "Webhooks" ], "security": [ { @@ -6291,10 +5754,10 @@ "name": "id", "schema": { "type": "string", - "description": "ID of the wordlist to update" + "description": "The ID of the webhook to get." }, "required": true, - "description": "ID of the wordlist to update" + "description": "The ID of the webhook to get." }, { "in": "header", @@ -6307,44 +5770,9 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "description": "New name for the wordlist", - "type": "string" - }, - "key": { - "description": "New key for the wordlist", - "type": "string" - }, - "description": { - "description": "New description for the wordlist", - "type": "string" - }, - "words": { - "description": "New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored.", - "type": "array", - "items": { - "type": "string" - } - }, - "strict": { - "description": "Deprecated. Now using threshold in project settings.", - "type": "boolean" - } - } - } - } - } - }, "responses": { "200": { - "description": "Wordlist updated successfully", + "description": "Webhook retrieved successfully", "content": { "application/json": { "schema": { @@ -6352,74 +5780,70 @@ "properties": { "id": { "type": "string", - "description": "ID of the wordlist" + "description": "The ID of the webhook." }, "name": { - "anyOf": [ - { - "type": "string", - "description": "Name of the wordlist" - }, - { - "type": "null" - } + "type": "string", + "description": "The webhook's name." + }, + "description": { + "description": "The webhook's description.", + "type": [ + "string", + "null" ] }, - "words": { + "url": { + "type": "string", + "description": "The URL we call when a subscribed event occurs." + }, + "eventTypes": { + "default": [], + "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", "type": "array", "items": { - "type": "string" - }, - "description": "Words in the wordlist" - }, - "createdAt": { - "description": "Creation date of the wordlist", - "type": "string" + "type": "string", + "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" + ] + } }, - "organizationId": { + "payloadVersion": { "type": "string", - "description": "ID of the organization" - }, - "userId": { - "anyOf": [ - { - "type": "string", - "description": "ID of the user" - }, - { - "type": "null" - } - ] + "enum": [ + "V1", + "V2" + ], + "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." }, - "strict": { - "type": "boolean", - "description": "Strict mode" + "createdAt": { + "description": "The date the webhook was created.", + "type": "string" } }, "required": [ "id", "name", - "words", - "createdAt", - "organizationId", - "userId", - "strict" + "url", + "eventTypes", + "payloadVersion", + "createdAt" ], "additionalProperties": false } } } }, - "400": { - "description": "Invalid input data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "401": { "description": "Authorization not provided", "content": { @@ -6451,25 +5875,23 @@ } } }, - "x-speakeasy-group": "wordlist", - "x-speakeasy-name-override": "update", + "x-speakeasy-group": "webhooks", + "x-speakeasy-name-override": "retrieve", "x-speakeasy-mcp": { - "title": "Update wordlist", + "title": "Get a webhook", "scopes": [ - "write" + "read" ], - "readOnlyHint": false, + "readOnlyHint": true, "destructiveHint": false } - } - }, - "/wordlist/{id}/embedding-status": { - "get": { - "operationId": "getWordlistEmbeddingStatus", - "summary": "Get embedding status", - "description": "Get the current embedding progress status for a wordlist", + }, + "put": { + "operationId": "updateWebhook", + "summary": "Update a webhook", + "description": "Update a webhook. Legacy v1 webhooks are read-only: delete them and create a new webhook instead.", "tags": [ - "Wordlists" + "Webhooks" ], "security": [ { @@ -6482,10 +5904,10 @@ "name": "id", "schema": { "type": "string", - "description": "ID of the wordlist to check embedding status for" + "description": "The ID of the webhook to update." }, "required": true, - "description": "ID of the wordlist to check embedding status for" + "description": "The ID of the webhook to update." }, { "in": "header", @@ -6498,34 +5920,126 @@ "x-speakeasy-name-override": "projectId" } ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "progress": { - "type": "number", - "description": "Percentage of words that have been embedded (0-100)" - }, - "totalWords": { - "type": "number", - "description": "Total number of words in the wordlist" - }, - "remainingWords": { - "type": "number", - "description": "Number of words still waiting to be embedded" - } - }, - "required": [ - "progress", - "totalWords", - "remainingWords" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "The webhook's name, used to identify it in the dashboard" + }, + "description": { + "description": "The webhook's description", + "type": [ + "string", + "null" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "The webhook's URL. We'll call this URL when an event occurs." + }, + "eventTypes": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "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" + ] + }, + "description": "Event types this webhook subscribes to. One webhook URL receives all events you list here." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Webhook updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the webhook." + }, + "name": { + "type": "string", + "description": "The webhook's name." + }, + "description": { + "description": "The webhook's description.", + "type": [ + "string", + "null" + ] + }, + "url": { + "type": "string", + "description": "The URL we call when a subscribed event occurs." + }, + "eventTypes": { + "default": [], + "description": "Event types this webhook subscribes to. Empty for legacy v1 webhooks, which subscribe via their single deprecated `type` instead.", + "type": "array", + "items": { + "type": "string", + "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" + ] + } + }, + "payloadVersion": { + "type": "string", + "enum": [ + "V1", + "V2" + ], + "description": "Payload envelope version. V2 is the Stripe-style envelope; V1 is the legacy flat shape and is read-only via this API." + }, + "createdAt": { + "description": "The date the webhook was created.", + "type": "string" + } + }, + "required": [ + "id", + "name", + "url", + "eventTypes", + "payloadVersion", + "createdAt" ], - "additionalProperties": false, - "description": "Embedding status details" + "additionalProperties": false } } } @@ -6550,16 +6064,6 @@ } } }, - "403": { - "description": "Insufficient access", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -6579,37 +6083,25 @@ } } } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InternalServerError" - } - } - } } }, - "x-speakeasy-group": "wordlist", - "x-speakeasy-name-override": "getEmbeddingStatus", + "x-speakeasy-group": "webhooks", + "x-speakeasy-name-override": "update", "x-speakeasy-mcp": { - "title": "Get embedding status", + "title": "Update a webhook", "scopes": [ - "read" + "destructive" ], - "readOnlyHint": true, - "destructiveHint": false + "readOnlyHint": false, + "destructiveHint": true } - } - }, - "/wordlist/{id}/words": { - "post": { - "operationId": "addWordlistWords", - "summary": "Add words to wordlist", - "description": "Add words to an existing wordlist", + }, + "delete": { + "operationId": "deleteWebhook", + "summary": "Delete a webhook", + "description": "Delete a webhook.", "tags": [ - "Wordlists" + "Webhooks" ], "security": [ { @@ -6622,10 +6114,10 @@ "name": "id", "schema": { "type": "string", - "description": "ID of the wordlist to add words to" + "description": "The ID of the webhook to delete." }, "required": true, - "description": "ID of the wordlist to add words to" + "description": "The ID of the webhook to delete." }, { "in": "header", @@ -6638,78 +6130,32 @@ "x-speakeasy-name-override": "projectId" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "words": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of words to add to the wordlist. Duplicate words will be ignored." - } - }, - "required": [ - "words" - ] - }, - "example": { - "words": [ - "free money", - "click here" - ] - } - } - } - }, "responses": { "200": { - "description": "Words successfully added to wordlist", + "description": "Webhook deleted successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "addedCount": { - "type": "number", - "description": "Number of words added" - }, - "totalCount": { - "type": "number", - "description": "Total number of words in wordlist" + "deleted": { + "type": "boolean", + "description": "Whether the webhook was deleted." }, - "addedWords": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of words that were added" + "id": { + "type": "string", + "description": "The ID of the webhook." } }, "required": [ - "addedCount", - "totalCount", - "addedWords" + "deleted", + "id" ], "additionalProperties": false } } } }, - "400": { - "description": "Invalid input data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "401": { "description": "Authorization not provided", "content": { @@ -6741,53 +6187,32 @@ } } }, - "x-speakeasy-group": "wordlist.words", - "x-speakeasy-name-override": "add", + "x-speakeasy-group": "webhooks", + "x-speakeasy-name-override": "delete", "x-speakeasy-mcp": { - "title": "Add words to wordlist", + "title": "Delete a webhook", "scopes": [ - "write" + "destructive" ], "readOnlyHint": false, - "destructiveHint": false + "destructiveHint": true } - }, - "delete": { - "operationId": "removeWordlistWords", - "summary": "Remove words from wordlist", - "description": "Remove words from an existing wordlist", - "tags": [ - "Wordlists" - ], - "security": [ - { + } + }, + "/webhook-secret": { + "get": { + "operationId": "getWebhookSecret", + "summary": "Get the webhook signing secret", + "description": "Get the signing secret used to sign webhook deliveries for this project, creating one if none exists yet. Verify deliveries by comparing the `modapi-signature` header to HMAC-SHA256(raw request body, secret) hex-encoded.", + "tags": [ + "Webhooks" + ], + "security": [ + { "Authorization": [] } ], "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "description": "ID of the wordlist to remove words from" - }, - "required": true, - "description": "ID of the wordlist to remove words from" - }, - { - "in": "query", - "name": "words", - "schema": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of words to remove from the wordlist" - }, - "required": true, - "description": "Array of words to remove from the wordlist" - }, { "in": "header", "name": "Project-Id", @@ -6801,48 +6226,25 @@ ], "responses": { "200": { - "description": "Words successfully removed from wordlist", + "description": "Signing secret retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { - "removedCount": { - "type": "number", - "description": "Number of words removed" - }, - "totalCount": { - "type": "number", - "description": "Total number of words in wordlist" - }, - "removedWords": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of words removed" + "secret": { + "type": "string", + "description": "The signing secret for this project. Every webhook delivery is signed with HMAC-SHA256 over the raw JSON body, hex-encoded in the `modapi-signature` header." } }, "required": [ - "removedCount", - "totalCount", - "removedWords" + "secret" ], "additionalProperties": false } } } }, - "400": { - "description": "Invalid input data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "401": { "description": "Authorization not provided", "content": { @@ -6853,16 +6255,6 @@ } } }, - "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": { @@ -6874,22 +6266,18 @@ } } }, - "x-speakeasy-group": "wordlist.words", - "x-speakeasy-name-override": "remove", + "x-speakeasy-group": "webhookSecret", + "x-speakeasy-name-override": "retrieve", "x-speakeasy-mcp": { - "title": "Remove words from wordlist", - "scopes": [ - "destructive" - ], - "readOnlyHint": false, - "destructiveHint": true + "disabled": true } } }, - "/moderate": { + "/moderate/text": { "post": { - "operationId": "submitContent", - "summary": "Submit content", + "operationId": "moderateText", + "summary": "Analyze text", + "description": "(Deprecated \u2014 use https://api.moderationapi.com/v1/moderate) Analyze text content. Runs the same policy engine as POST /moderate; the per-model result blocks are approximated from the policies it ran.", "tags": [ "Content" ], @@ -6898,6 +6286,7 @@ "Authorization": [] } ], + "deprecated": true, "parameters": [ { "in": "header", @@ -6917,374 +6306,2472 @@ "schema": { "type": "object", "properties": { - "content": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "text" - }, - "text": { - "type": "string", - "maxLength": 100000, - "description": "The content text" - } + "contentId": { + "description": "The unique ID of the content in your database.", + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "channelKey": { + "description": "The key of the channel.", + "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" + }, + "authorId": { + "description": "The author of the content.", + "type": "string" + }, + "contextId": { + "description": "For example the ID of a chat room or a post", + "type": "string" + }, + "metadata": { + "description": "Any metadata you want to store with the content", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "value": { + "type": "string", + "maxLength": 100000, + "description": "The text you'd like to analyze. We recommend to submit plain text or HTML" + } + }, + "required": [ + "value" + ], + "description": "Input parameters for text moderation" + } + } + } + }, + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Success if the request was successful" }, - "required": [ - "type", - "text" - ], - "description": "Text", - "title": "Text" - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "image" - }, - "url": { - "description": "A public URL of the image content. Either url or data must be provided.", - "type": "string", - "format": "uri" - }, - "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.", - "type": "string", - "maxLength": 5500000 - } + "contentId": { + "description": "The ID of the content.", + "type": "string" }, - "required": [ - "type" - ], - "description": "Image", - "title": "Image" - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "video" + "request": { + "type": "object", + "properties": { + "timestamp": { + "type": "number", + "description": "The timestamp of the request" + }, + "quota_usage": { + "type": "number", + "description": "The quota usage of the request" + } }, - "url": { - "type": "string", - "format": "uri", - "description": "A public URL of the video content" - } + "required": [ + "timestamp", + "quota_usage" + ], + "additionalProperties": false, + "description": "Information about the request" }, - "required": [ - "type", - "url" - ], - "description": "Video", - "title": "Video" - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "audio" - }, - "url": { - "type": "string", - "format": "uri", - "description": "The URL of the audio content" - } + "flagged": { + "type": "boolean", + "description": "Whether the content was flagged by any models" }, - "required": [ - "type", - "url" - ], - "description": "Audio", - "title": "Audio" - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "data": { - "type": "object", - "propertyNames": { - "type": "string", - "description": "The key of the content node" - }, - "additionalProperties": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "text" - }, - "text": { - "type": "string", - "maxLength": 100000, - "description": "The content text" - } - }, - "required": [ - "type", - "text" - ], - "description": "Text", - "title": "Text" + "author": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Author ID in Moderation API" }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "image" - }, - "url": { - "description": "A public URL of the image content. Either url or data must be provided.", - "type": "string", - "format": "uri" + "external_id": { + "description": "The author's ID from your system", + "type": [ + "string", + "null" + ] + }, + "block": { + "anyOf": [ + { + "type": "object", + "properties": { + "until": { + "description": "The timestamp until which they are blocked if the author is suspended.", + "type": [ + "number", + "null" + ] + }, + "reason": { + "description": "The moderators reason why the author was blocked or suspended.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false }, - "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.", - "type": "string", - "maxLength": 5500000 + { + "type": "null" } - }, - "required": [ - "type" ], - "description": "Image", - "title": "Image" + "description": "Block or suspension details, if applicable. Null if the author is enabled." }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "video" - }, - "url": { - "type": "string", - "format": "uri", - "description": "A public URL of the video content" - } - }, - "required": [ - "type", - "url" + "status": { + "type": "string", + "enum": [ + "enabled", + "suspended", + "blocked" ], - "description": "Video", - "title": "Video" + "description": "Current author status" }, - { + "trust_level": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "audio" + "level": { + "type": "number", + "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" }, - "url": { - "type": "string", - "format": "uri", - "description": "The URL of the audio content" + "manual": { + "type": "boolean", + "description": "True if the trust level was set manually by a moderator" } }, "required": [ - "type", - "url" + "level", + "manual" ], - "description": "Audio", - "title": "Audio" + "additionalProperties": false } + }, + "required": [ + "id", + "block", + "status", + "trust_level" ], - "title": "Content leaf", - "type": "object" + "additionalProperties": false }, - "description": "Values in the object. Can be mixed content types." - } + { + "type": "null" + } + ], + "description": "The author of the content if your account has authors enabled. Requires you to send authorId when submitting content." }, - "required": [ - "type", - "data" - ], - "description": "Object", - "title": "Object" - } - ], - "description": "The content sent for moderation", - "type": "object" - }, - "timestamp": { - "description": "Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.", - "type": "number" - }, - "channel": { - "description": "Provide a channel ID or key. Will use the project's default channel if not provided.", - "type": "string" - }, - "contentId": { - "description": "The unique ID of the content in your database.", - "type": "string", - "maxLength": 100 - }, - "metaType": { - "description": "The meta type of content being moderated", - "type": "string", - "enum": [ - "profile", - "message", - "post", - "comment", - "event", - "product", - "review", - "voice", - "other" - ] - }, - "authorId": { - "description": "The author of the content.", - "type": "string" - }, - "conversationId": { - "description": "For example the ID of a chat room or a post", - "type": "string" - }, - "metadata": { - "description": "Any metadata you want to store with the content", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "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.", - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "review", - "allow", - "reject" - ], - "description": "Your recommendation for the content: allow, review, or reject." - }, - "behavior": { - "description": "How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.", - "type": "string", - "enum": [ - "override", - "escalate" - ] + "content_moderated": { + "type": "boolean", + "description": "Whether the content was moderated or not. Same as `content` !== `original`" + }, + "unicode_spoofing": { + "type": "boolean", + "description": "Whether the content is using look-alike characters. Often used by spammers." + }, + "data_found": { + "type": "boolean", + "description": "Whether any entity matchers found data for the content" + }, + "original": { + "type": "string", + "description": "The original content" + }, + "content": { + "type": "string", + "description": "The content after moderation. With all mask replacements applied and look-alike characters replaced with the original characters." + } }, - "source": { - "description": "Where your recommendation came from, e.g. \"banned-ip\".", - "type": "string", - "maxLength": 100 - }, - "reason": { - "description": "A human-readable explanation for your recommendation.", - "type": "string", - "maxLength": 500 - } + "required": [ + "status", + "request", + "flagged", + "author", + "content_moderated", + "unicode_spoofing", + "data_found", + "original", + "content" + ], + "additionalProperties": false }, - "required": [ - "action" - ] + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + ] + } + } + } + }, + "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" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-ignore": true + } + }, + "/moderate/image": { + "post": { + "operationId": "moderateImage", + "summary": "Analyze image", + "description": "(Deprecated \u2014 use https://api.moderationapi.com/v1/moderate) Analyze an image. Runs the same policy engine as POST /moderate; `labels` is approximated from the policies it ran and `texts` is no longer populated.", + "tags": [ + "Content" + ], + "security": [ + { + "Authorization": [] + } + ], + "deprecated": true, + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contentId": { + "description": "The unique ID of the content in your database.", + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "channelKey": { + "description": "The key of the channel.", + "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" }, - "policies": { - "description": "(Enterprise) override the channel policies for this moderation request only.", - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "properties": { - "flag": { - "type": "boolean" - }, - "threshold": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "id": { - "type": "string", - "const": "toxicity" - } - }, - "required": [ - "flag", - "id" - ], - "title": "Toxicity" + "authorId": { + "description": "The author of the content.", + "type": "string" + }, + "contextId": { + "description": "For example the ID of a chat room or a post", + "type": "string" + }, + "metadata": { + "description": "Any metadata you want to store with the content", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL of the image you want to analyze." + } + }, + "required": [ + "url" + ], + "description": "Input parameters for image moderation" + } + } + } + }, + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Success if the request was successful" }, - { - "type": "object", - "properties": { - "flag": { - "type": "boolean" - }, - "threshold": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "id": { - "type": "string", - "const": "personal_information" - } - }, - "required": [ - "flag", - "id" - ], - "title": "Personal Information" + "contentId": { + "description": "The ID of the content.", + "type": "string" }, - { + "request": { "type": "object", "properties": { - "flag": { - "type": "boolean" - }, - "threshold": { + "timestamp": { "type": "number", - "minimum": 0, - "maximum": 1 + "description": "The timestamp of the request" }, - "id": { - "type": "string", - "const": "hate" + "quota_usage": { + "type": "number", + "description": "The quota usage of the request" } }, "required": [ - "flag", - "id" + "timestamp", + "quota_usage" ], - "title": "Hate" + "additionalProperties": false, + "description": "Information about the request" }, - { - "type": "object", - "properties": { - "flag": { - "type": "boolean" - }, - "threshold": { - "type": "number", - "minimum": 0, - "maximum": 1 + "flagged": { + "type": "boolean", + "description": "Whether the content was flagged by any models" + }, + "author": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Author ID in Moderation API" + }, + "external_id": { + "description": "The author's ID from your system", + "type": [ + "string", + "null" + ] + }, + "block": { + "anyOf": [ + { + "type": "object", + "properties": { + "until": { + "description": "The timestamp until which they are blocked if the author is suspended.", + "type": [ + "number", + "null" + ] + }, + "reason": { + "description": "The moderators reason why the author was blocked or suspended.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ], + "description": "Block or suspension details, if applicable. Null if the author is enabled." + }, + "status": { + "type": "string", + "enum": [ + "enabled", + "suspended", + "blocked" + ], + "description": "Current author status" + }, + "trust_level": { + "type": "object", + "properties": { + "level": { + "type": "number", + "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" + }, + "manual": { + "type": "boolean", + "description": "True if the trust level was set manually by a moderator" + } + }, + "required": [ + "level", + "manual" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "block", + "status", + "trust_level" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ], + "description": "The author of the content if your account has authors enabled. Requires you to send authorId when submitting content." + }, + "data_found": { + "type": "boolean", + "description": "Whether any entity matchers found data for the content" + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "required": [ + "label", + "score" + ], + "additionalProperties": false + }, + "description": "Legacy image labels, approximated from the equivalent policies. Use POST /moderate for the policy probabilities themselves." + } + }, + "required": [ + "status", + "request", + "flagged", + "author", + "data_found", + "labels" + ], + "additionalProperties": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + ] + } + } + } + }, + "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" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-ignore": true + } + }, + "/account": { + "get": { + "operationId": "getAccount", + "summary": "Get account details", + "description": "Get account details", + "tags": [ + "Account" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Account details retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the account" + }, + "paid_plan_name": { + "type": "string", + "description": "Name of the paid plan" + }, + "text_api_quota": { + "type": "number", + "description": "Text API quota" + }, + "remaining_quota": { + "type": "number", + "description": "Remaining quota" + }, + "current_project": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the current project" + }, + "name": { + "type": "string", + "description": "Name of the current project" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "paid_plan_name", + "text_api_quota", + "remaining_quota" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "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": "account", + "x-speakeasy-name-override": "retrieve", + "x-speakeasy-mcp": { + "title": "Get account details", + "scopes": [ + "read" + ], + "readOnlyHint": true, + "destructiveHint": false + } + } + }, + "/auth": { + "get": { + "operationId": "getAuth", + "tags": [ + "Account" + ], + "security": [ + { + "Authorization": [] + } + ], + "deprecated": true, + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Status of the authentication" + }, + "message": { + "type": "string", + "description": "Message of the authentication" + } + }, + "required": [ + "status", + "message" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-ignore": true + }, + "post": { + "operationId": "createAuth", + "tags": [ + "Account" + ], + "security": [ + { + "Authorization": [] + } + ], + "deprecated": true, + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Status of the authentication" + }, + "message": { + "type": "string", + "description": "Message of the authentication" + }, + "project": { + "type": "string", + "description": "Name of the authenticated project" + } + }, + "required": [ + "status", + "message", + "project" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-ignore": true + } + }, + "/wordlist": { + "get": { + "operationId": "listWordlists", + "summary": "List wordlists", + "description": "List the wordlists of the organization. The list is paginated.", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "page", + "schema": { + "default": 1, + "description": "Page number, starting at 1.", + "type": "integer", + "minimum": 1, + "maximum": 100000 + } + }, + { + "in": "query", + "name": "pageSize", + "schema": { + "default": 20, + "description": "Number of items per page. Maximum 100.", + "type": "integer", + "minimum": 1, + "maximum": 100 + } + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the wordlist" + }, + "name": { + "description": "Name of the wordlist", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "Description of the wordlist", + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "description": "When the wordlist was created", + "type": [ + "string", + "null" + ] + }, + "userId": { + "description": "User who created the wordlist", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "name", + "description", + "createdAt", + "userId" + ], + "additionalProperties": false + }, + "description": "The items on this page." + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "required": [ + "data", + "pagination" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-group": "wordlist", + "x-speakeasy-name-override": "list", + "x-speakeasy-mcp": { + "title": "List wordlists", + "scopes": [ + "read" + ], + "readOnlyHint": true, + "destructiveHint": false + }, + "x-speakeasy-pagination": { + "type": "offsetLimit", + "inputs": [ + { + "name": "page", + "in": "parameters", + "type": "page" + }, + { + "name": "pageSize", + "in": "parameters", + "type": "limit" + } + ], + "outputs": { + "results": "$.data", + "numPages": "$.pagination.totalPages" + } + } + }, + "post": { + "operationId": "createWordlist", + "summary": "Create wordlist", + "description": "Create a wordlist in the organization. You can add words now or later. Each word is embedded in the background for similarity matching. Use the embedding status endpoint to follow the progress. To use the wordlist in moderation, add it to a policy.", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Name of the wordlist" + }, + "key": { + "description": "Key of the wordlist. Default: made from the name. If the key is already in use, a suffix is added.", + "type": "string" + }, + "description": { + "description": "Description of the wordlist", + "type": "string" + }, + "words": { + "description": "Words to add to the wordlist. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Wordlist created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the wordlist" + }, + "name": { + "anyOf": [ + { + "type": "string", + "description": "Name of the wordlist" + }, + { + "type": "null" + } + ] + }, + "words": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words in the wordlist" + }, + "createdAt": { + "description": "Creation date of the wordlist", + "type": "string" + }, + "organizationId": { + "type": "string", + "description": "ID of the organization" + }, + "userId": { + "anyOf": [ + { + "type": "string", + "description": "ID of the user" + }, + { + "type": "null" + } + ] + }, + "strict": { + "type": "boolean", + "description": "Strict mode" + } + }, + "required": [ + "id", + "name", + "words", + "createdAt", + "organizationId", + "userId", + "strict" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "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": "wordlist", + "x-speakeasy-name-override": "create", + "x-speakeasy-mcp": { + "title": "Create wordlist", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + } + }, + "/wordlist/{id}": { + "get": { + "operationId": "getWordlist", + "summary": "Get wordlist", + "description": "Get a specific wordlist by ID", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "ID of the wordlist to get" + }, + "required": true, + "description": "ID of the wordlist to get" + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the wordlist" + }, + "name": { + "anyOf": [ + { + "type": "string", + "description": "Name of the wordlist" + }, + { + "type": "null" + } + ] + }, + "words": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words in the wordlist" + }, + "createdAt": { + "description": "Creation date of the wordlist", + "type": "string" + }, + "organizationId": { + "type": "string", + "description": "ID of the organization" + }, + "userId": { + "anyOf": [ + { + "type": "string", + "description": "ID of the user" + }, + { + "type": "null" + } + ] + }, + "strict": { + "type": "boolean", + "description": "Strict mode" + } + }, + "required": [ + "id", + "name", + "words", + "createdAt", + "organizationId", + "userId", + "strict" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-group": "wordlist", + "x-speakeasy-name-override": "retrieve", + "x-speakeasy-mcp": { + "title": "Get wordlist", + "scopes": [ + "read" + ], + "readOnlyHint": true, + "destructiveHint": false + } + }, + "put": { + "operationId": "updateWordlist", + "summary": "Update wordlist", + "description": "Update a wordlist. Fields that you do not send stay the same. If you send words, they replace all existing words, and the embeddings are updated in the background.", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "ID of the wordlist to update" + }, + "required": true, + "description": "ID of the wordlist to update" + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "description": "New name for the wordlist", + "type": "string" + }, + "key": { + "description": "New key for the wordlist", + "type": "string" + }, + "description": { + "description": "New description for the wordlist", + "type": "string" + }, + "words": { + "description": "New words for the wordlist. Replace the existing words with these new ones. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words.", + "type": "array", + "items": { + "type": "string" + } + }, + "strict": { + "description": "Deprecated. Now using threshold in project settings.", + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Wordlist updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the wordlist" + }, + "name": { + "anyOf": [ + { + "type": "string", + "description": "Name of the wordlist" + }, + { + "type": "null" + } + ] + }, + "words": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words in the wordlist" + }, + "createdAt": { + "description": "Creation date of the wordlist", + "type": "string" + }, + "organizationId": { + "type": "string", + "description": "ID of the organization" + }, + "userId": { + "anyOf": [ + { + "type": "string", + "description": "ID of the user" + }, + { + "type": "null" + } + ] + }, + "strict": { + "type": "boolean", + "description": "Strict mode" + } + }, + "required": [ + "id", + "name", + "words", + "createdAt", + "organizationId", + "userId", + "strict" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "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": "wordlist", + "x-speakeasy-name-override": "update", + "x-speakeasy-mcp": { + "title": "Update wordlist", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/wordlist/{id}/embedding-status": { + "get": { + "operationId": "getWordlistEmbeddingStatus", + "summary": "Get embedding status", + "description": "Get the current embedding progress status for a wordlist", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "ID of the wordlist to check embedding status for" + }, + "required": true, + "description": "ID of the wordlist to check embedding status for" + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "progress": { + "type": "number", + "description": "Percentage of words that have been embedded (0-100)" + }, + "totalWords": { + "type": "number", + "description": "Total number of words in the wordlist" + }, + "remainingWords": { + "type": "number", + "description": "Number of words still waiting to be embedded" + } + }, + "required": [ + "progress", + "totalWords", + "remainingWords" + ], + "additionalProperties": false, + "description": "Embedding status details" + } + } + } + }, + "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" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerError" + } + } + } + } + }, + "x-speakeasy-group": "wordlist", + "x-speakeasy-name-override": "getEmbeddingStatus", + "x-speakeasy-mcp": { + "title": "Get embedding status", + "scopes": [ + "read" + ], + "readOnlyHint": true, + "destructiveHint": false + } + } + }, + "/wordlist/{id}/words": { + "post": { + "operationId": "addWordlistWords", + "summary": "Add words to wordlist", + "description": "Add words to an existing wordlist", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "ID of the wordlist to add words to" + }, + "required": true, + "description": "ID of the wordlist to add words to" + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "words": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of words to add to the wordlist. Duplicate words will be ignored." + } + }, + "required": [ + "words" + ] + }, + "example": { + "words": [ + "free money", + "click here" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Words successfully added to wordlist", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addedCount": { + "type": "number", + "description": "Number of words added" + }, + "totalCount": { + "type": "number", + "description": "Total number of words in wordlist" + }, + "addedWords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of words that were added" + } + }, + "required": [ + "addedCount", + "totalCount", + "addedWords" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "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": "wordlist.words", + "x-speakeasy-name-override": "add", + "x-speakeasy-mcp": { + "title": "Add words to wordlist", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + }, + "delete": { + "operationId": "removeWordlistWords", + "summary": "Remove words from wordlist", + "description": "Remove words from an existing wordlist", + "tags": [ + "Wordlists" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "ID of the wordlist to remove words from" + }, + "required": true, + "description": "ID of the wordlist to remove words from" + }, + { + "in": "query", + "name": "words", + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of words to remove from the wordlist" + }, + "required": true, + "description": "Array of words to remove from the wordlist" + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "responses": { + "200": { + "description": "Words successfully removed from wordlist", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "removedCount": { + "type": "number", + "description": "Number of words removed" + }, + "totalCount": { + "type": "number", + "description": "Total number of words in wordlist" + }, + "removedWords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of words removed" + } + }, + "required": [ + "removedCount", + "totalCount", + "removedWords" + ], + "additionalProperties": false + } + } + } + }, + "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" + } + } + } + }, + "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": "wordlist.words", + "x-speakeasy-name-override": "remove", + "x-speakeasy-mcp": { + "title": "Remove words from wordlist", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/moderate": { + "post": { + "operationId": "submitContent", + "summary": "Submit content", + "tags": [ + "Content" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "content": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string", + "maxLength": 100000, + "description": "The content text" + } + }, + "required": [ + "type", + "text" + ], + "description": "Text", + "title": "Text" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "url": { + "description": "A public URL of the image content. Either url or data must be provided.", + "type": "string", + "format": "uri" + }, + "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.", + "type": "string", + "maxLength": 5500000 + } + }, + "required": [ + "type" + ], + "description": "Image", + "title": "Image" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "video" + }, + "url": { + "type": "string", + "format": "uri", + "description": "A public URL of the video content" + } + }, + "required": [ + "type", + "url" + ], + "description": "Video", + "title": "Video" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "audio" + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL of the audio content" + } + }, + "required": [ + "type", + "url" + ], + "description": "Audio", + "title": "Audio" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "object" + }, + "data": { + "type": "object", + "propertyNames": { + "type": "string", + "description": "The key of the content node" + }, + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string", + "maxLength": 100000, + "description": "The content text" + } + }, + "required": [ + "type", + "text" + ], + "description": "Text", + "title": "Text" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "url": { + "description": "A public URL of the image content. Either url or data must be provided.", + "type": "string", + "format": "uri" + }, + "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.", + "type": "string", + "maxLength": 5500000 + } + }, + "required": [ + "type" + ], + "description": "Image", + "title": "Image" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "video" + }, + "url": { + "type": "string", + "format": "uri", + "description": "A public URL of the video content" + } + }, + "required": [ + "type", + "url" + ], + "description": "Video", + "title": "Video" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "audio" + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL of the audio content" + } + }, + "required": [ + "type", + "url" + ], + "description": "Audio", + "title": "Audio" + } + ], + "title": "Content leaf", + "type": "object" + }, + "description": "Values in the object. Can be mixed content types." + } + }, + "required": [ + "type", + "data" + ], + "description": "Object", + "title": "Object" + } + ], + "description": "The content sent for moderation", + "type": "object" + }, + "timestamp": { + "description": "Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.", + "type": "number" + }, + "channel": { + "description": "Provide a channel ID or key. Will use the project's default channel if not provided.", + "type": "string" + }, + "contentId": { + "description": "The unique ID of the content in your database.", + "type": "string", + "maxLength": 100 + }, + "metaType": { + "description": "The meta type of content being moderated", + "type": "string", + "enum": [ + "profile", + "message", + "post", + "comment", + "event", + "product", + "review", + "voice", + "other" + ] + }, + "authorId": { + "description": "The author of the content.", + "type": "string" + }, + "conversationId": { + "description": "For example the ID of a chat room or a post", + "type": "string" + }, + "metadata": { + "description": "Any metadata you want to store with the content", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "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.", + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "review", + "allow", + "reject" + ], + "description": "Your recommendation for the content: allow, review, or reject." + }, + "behavior": { + "description": "How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.", + "type": "string", + "enum": [ + "override", + "escalate" + ] + }, + "source": { + "description": "Where your recommendation came from, e.g. \"banned-ip\".", + "type": "string", + "maxLength": 100 + }, + "reason": { + "description": "A human-readable explanation for your recommendation.", + "type": "string", + "maxLength": 500 + } + }, + "required": [ + "action" + ] + }, + "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" + }, + "policies": { + "description": "(Enterprise) override the channel policies for this moderation request only.", + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "flag": { + "type": "boolean" + }, + "threshold": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "id": { + "type": "string", + "const": "toxicity" + } + }, + "required": [ + "flag", + "id" + ], + "title": "Toxicity" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "boolean" + }, + "threshold": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "id": { + "type": "string", + "const": "personal_information" + } + }, + "required": [ + "flag", + "id" + ], + "title": "Personal Information" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "boolean" + }, + "threshold": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "id": { + "type": "string", + "const": "hate" + } + }, + "required": [ + "flag", + "id" + ], + "title": "Hate" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "boolean" + }, + "threshold": { + "type": "number", + "minimum": 0, + "maximum": 1 }, "id": { "type": "string", @@ -9776,10 +11263,10 @@ "x-speakeasy-mcp": { "title": "Update a project", "scopes": [ - "write" + "destructive" ], "readOnlyHint": false, - "destructiveHint": false + "destructiveHint": true } }, "delete": { @@ -10704,10 +12191,10 @@ "x-speakeasy-mcp": { "title": "Update a channel", "scopes": [ - "write" + "destructive" ], "readOnlyHint": false, - "destructiveHint": false + "destructiveHint": true } }, "delete": { @@ -10884,404 +12371,953 @@ } } }, - "400": { - "description": "Invalid input data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } + "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": "channels", + "x-speakeasy-name-override": "duplicate", + "x-speakeasy-mcp": { + "title": "Duplicate a channel", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + } + }, + "/stream": { + "get": { + "tags": [ + "Voice" + ], + "summary": "Real-time voice moderation stream (WebSocket)", + "description": "Open a WebSocket to moderate live voice/call audio in real time. Speech is transcribed and each finalized utterance is moderated by your enabled text policies; you receive a verdict per utterance as it's spoken.\n\n**This is a WebSocket upgrade, not a regular HTTP call.** The request body below documents the frames you *send* over the socket; the `101` response documents the events you *receive*.\n\n- **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid key closes `4401`; voice not enabled on the plan/channel closes `4403`.\n- **Subprotocol:** request `moderationapi.v1`.\n- **Flow:** send one `start` frame, then `media` frames as audio arrives, then `stop` (or disconnect). You receive `session.started`, `utterance.final` per utterance, optional `utterance.partial`/`warning`, and `session.ended`.\n- **Close codes:** `1000` normal \u00b7 `1011` server error \u00b7 `4400` bad request \u00b7 `4401` auth failed \u00b7 `4403` voice not enabled \u00b7 `4429` concurrency limit.\n\nSee the [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice) for the full walkthrough and code examples.", + "servers": [ + { + "url": "wss://voice.moderationapi.com/v1", + "description": "Voice streaming gateway" + } + ], + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "description": "Bearer ", + "schema": { + "type": "string" + } + }, + { + "name": "Sec-WebSocket-Protocol", + "in": "header", + "required": true, + "description": "Requested subprotocol.", + "schema": { + "type": "string", + "enum": [ + "moderationapi.v1" + ] + } + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "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.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "description": "Frames sent by the client over the socket (not an HTTP body).", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/VoiceStartFrame" + }, + { + "$ref": "#/components/schemas/VoiceMediaFrame" + }, + { + "$ref": "#/components/schemas/VoiceStopFrame" + } + ], + "discriminator": { + "propertyName": "event" + } + } + } + } + }, + "responses": { + "101": { + "description": "Switching Protocols. The server then streams event frames over the socket; the key one is `utterance.final`.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/VoiceSessionStarted" + }, + { + "$ref": "#/components/schemas/VoiceUtteranceFinal" + }, + { + "$ref": "#/components/schemas/VoiceSessionEnded" + } + ], + "discriminator": { + "propertyName": "event" + } + } + } + } + } + }, + "x-webSocket": true, + "operationId": "streamVoice", + "x-speakeasy-ignore": true + } + } + }, + "components": { + "schemas": { + "PublicAuthor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Author ID in Moderation API" + }, + "external_id": { + "description": "The author's ID from your system", + "type": [ + "string", + "null" + ] + }, + "profile_picture": { + "description": "URL of the author's profile picture", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "external_link": { + "description": "URL of the author's external profile", + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "name": { + "description": "Author name or identifier", + "type": [ + "string", + "null" + ] + }, + "email": { + "description": "Author email address", + "anyOf": [ + { + "type": "string", + "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": "null" + } + ] + }, + "company": { + "description": "The author's company or organization", + "type": [ + "string", + "null" + ] + }, + "first_seen": { + "type": "number", + "description": "Timestamp when author first appeared" + }, + "last_seen": { + "type": "number", + "description": "Timestamp of last activity" + }, + "last_incident": { + "description": "Timestamp of last incident", + "type": [ + "number", + "null" + ] + }, + "status": { + "type": "string", + "enum": [ + "enabled", + "suspended", + "blocked" + ], + "description": "Current author status" + }, + "trust_level": { + "type": "object", + "properties": { + "level": { + "type": "number", + "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" + }, + "manual": { + "type": "boolean", + "description": "True if the trust level was set manually by a moderator" } - } + }, + "required": [ + "level", + "manual" + ], + "additionalProperties": false }, - "401": { - "description": "Authorization not provided", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticationError" - } + "block": { + "anyOf": [ + { + "type": "object", + "properties": { + "until": { + "description": "The timestamp until which they are blocked if the author is suspended.", + "type": [ + "number", + "null" + ] + }, + "reason": { + "description": "The moderators reason why the author was blocked or suspended.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" } - } + ], + "description": "Block or suspension details, if applicable. Null if the author is enabled." }, - "403": { - "description": "Insufficient access", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PermissionDeniedError" - } + "risk_evaluation": { + "anyOf": [ + { + "type": "object", + "properties": { + "risk_level": { + "description": "Calculated risk level based on more than 10 behavioral signals.", + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" } - } + ], + "description": "Risk assessment details, if available." }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } + "metrics": { + "type": "object", + "properties": { + "total_content": { + "type": "number", + "description": "Total pieces of content" + }, + "flagged_content": { + "type": "number", + "description": "Number of flagged content pieces" + }, + "average_sentiment": { + "description": "Average sentiment score of content (-1 to 1). Requires a sentiment model in your project.", + "type": [ + "number", + "null" + ] } - } + }, + "required": [ + "total_content", + "flagged_content" + ], + "additionalProperties": false }, - "429": { - "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RateLimitError" - } + "metadata": { + "type": "object", + "properties": { + "email_verified": { + "description": "Whether the author's email is verified", + "type": [ + "boolean", + "null" + ] + }, + "phone_verified": { + "description": "Whether the author's phone number 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" + ] } - } + }, + "additionalProperties": {}, + "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." } }, - "x-speakeasy-group": "channels", - "x-speakeasy-name-override": "duplicate", - "x-speakeasy-mcp": { - "title": "Duplicate a channel", - "scopes": [ - "write" - ], - "readOnlyHint": false, - "destructiveHint": false - } - } - }, - "/stream": { - "get": { - "tags": [ - "Voice" + "required": [ + "id", + "first_seen", + "last_seen", + "status", + "trust_level", + "block", + "risk_evaluation", + "metrics", + "metadata" ], - "summary": "Real-time voice moderation stream (WebSocket)", - "description": "Open a WebSocket to moderate live voice/call audio in real time. Speech is transcribed and each finalized utterance is moderated by your enabled text policies; you receive a verdict per utterance as it's spoken.\n\n**This is a WebSocket upgrade, not a regular HTTP call.** The request body below documents the frames you *send* over the socket; the `101` response documents the events you *receive*.\n\n- **Auth:** `Authorization: Bearer ` on the upgrade. A missing/invalid key closes `4401`; voice not enabled on the plan/channel closes `4403`.\n- **Subprotocol:** request `moderationapi.v1`.\n- **Flow:** send one `start` frame, then `media` frames as audio arrives, then `stop` (or disconnect). You receive `session.started`, `utterance.final` per utterance, optional `utterance.partial`/`warning`, and `session.ended`.\n- **Close codes:** `1000` normal \u00b7 `1011` server error \u00b7 `4400` bad request \u00b7 `4401` auth failed \u00b7 `4403` voice not enabled \u00b7 `4429` concurrency limit.\n\nSee the [Real-time voice guide](https://docs.moderationapi.com/content-moderation/real-time-voice) for the full walkthrough and code examples.", - "servers": [ - { - "url": "wss://voice.moderationapi.com/v1", - "description": "Voice streaming gateway" + "additionalProperties": false + }, + "Queue": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The queue ID." + }, + "name": { + "type": "string", + "description": "The queue name." + }, + "description": { + "description": "The queue description.", + "type": [ + "string", + "null" + ] + }, + "isDefault": { + "type": "boolean", + "description": "Whether this is the default queue of the project." } + }, + "required": [ + "id", + "name", + "description", + "isDefault" ], - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "description": "Bearer ", - "schema": { - "type": "string" - } + "additionalProperties": false + }, + "QueueWithCounts": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The queue ID." }, - { - "name": "Sec-WebSocket-Protocol", - "in": "header", - "required": true, - "description": "Requested subprotocol.", - "schema": { - "type": "string", - "enum": [ - "moderationapi.v1" - ] - } + "name": { + "type": "string", + "description": "The queue name." + }, + "description": { + "description": "The queue description.", + "type": [ + "string", + "null" + ] + }, + "isDefault": { + "type": "boolean", + "description": "Whether this is the default queue of the project." }, - { - "in": "header", - "name": "Project-Id", - "required": false, - "schema": { - "type": "string" - }, - "description": "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.", - "x-speakeasy-name-override": "projectId" + "unresolvedItemsCount": { + "type": "integer", + "description": "Number of items that wait for review." + }, + "resolvedItemsCount": { + "type": "integer", + "description": "Number of items that are resolved." + }, + "totalItemsCount": { + "type": "integer", + "description": "Number of items in the queue, resolved and unresolved." } + }, + "required": [ + "id", + "name", + "description", + "isDefault", + "unresolvedItemsCount", + "resolvedItemsCount", + "totalItemsCount" ], - "requestBody": { - "description": "Frames sent by the client over the socket (not an HTTP body).", + "additionalProperties": false + }, + "QueueItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The item ID. Use it to resolve or unresolve the item in this queue." + }, + "contentId": { + "type": "string", + "description": "Your content ID, as you sent it to `POST /moderate`." + }, "content": { - "application/json": { - "schema": { + "anyOf": [ + { "oneOf": [ { - "$ref": "#/components/schemas/VoiceStartFrame" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string", + "maxLength": 100000, + "description": "The content text" + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text", + "title": "Text" }, { - "$ref": "#/components/schemas/VoiceMediaFrame" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "url": { + "description": "A public URL of the image content. Either url or data must be provided.", + "type": "string", + "format": "uri" + }, + "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.", + "type": "string", + "maxLength": 5500000 + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Image", + "title": "Image" }, { - "$ref": "#/components/schemas/VoiceStopFrame" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "video" + }, + "url": { + "type": "string", + "format": "uri", + "description": "A public URL of the video content" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false, + "description": "Video", + "title": "Video" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "audio" + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL of the audio content" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false, + "description": "Audio", + "title": "Audio" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "object" + }, + "data": { + "type": "object", + "propertyNames": { + "type": "string", + "description": "The key of the content node" + }, + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string", + "maxLength": 100000, + "description": "The content text" + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "description": "Text", + "title": "Text" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "url": { + "description": "A public URL of the image content. Either url or data must be provided.", + "type": "string", + "format": "uri" + }, + "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.", + "type": "string", + "maxLength": 5500000 + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Image", + "title": "Image" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "video" + }, + "url": { + "type": "string", + "format": "uri", + "description": "A public URL of the video content" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false, + "description": "Video", + "title": "Video" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "audio" + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL of the audio content" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": false, + "description": "Audio", + "title": "Audio" + } + ], + "title": "Content leaf", + "type": "object" + }, + "description": "Values in the object. Can be mixed content types." + } + }, + "required": [ + "type", + "data" + ], + "additionalProperties": false, + "description": "Object", + "title": "Object" } ], - "discriminator": { - "propertyName": "event" - } + "type": "object" + }, + { + "type": "null" } - } - } - }, - "responses": { - "101": { - "description": "Switching Protocols. The server then streams event frames over the socket; the key one is `utterance.final`.", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/VoiceSessionStarted" - }, - { - "$ref": "#/components/schemas/VoiceUtteranceFinal" + ], + "description": "The submitted content." + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "The label name" + }, + "score": { + "type": "number", + "description": "Confidence score between 0 and 1" + }, + "manual": { + "description": "True if the label was applied manually by a moderator", + "type": "boolean" + }, + "flagged": { + "description": "Whether this label crossed its flagging threshold", + "type": "boolean" + }, + "shadow_flagged": { + "description": "Whether this label crossed its threshold under a shadowed policy \u2014 reported for visibility, never counted toward a flagging decision", + "type": "boolean" + }, + "matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "match": { + "type": "string", + "description": "The matched substring" + }, + "probability": { + "type": "number", + "description": "Match confidence between 0 and 1" + }, + "span": { + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "items": false, + "minItems": 2, + "maxItems": 2, + "description": "[start, end] character offsets in the source text" + }, + "mask": { + "type": [ + "string", + "null" + ] + }, + "reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "signals": { + "type": "object", + "properties": { + "brand_impersonation": { + "anyOf": [ + { + "type": "object", + "properties": { + "brand": { + "type": "string" + }, + "method": { + "type": "string" + } + }, + "required": [ + "brand", + "method" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "has_suspicious_characters": { + "type": "boolean" + }, + "is_link_shortener": { + "type": "boolean" + }, + "domain_age_days": { + "type": [ + "number", + "null" + ] + }, + "has_email_setup": { + "type": [ + "boolean", + "null" + ] + }, + "redirect_count": { + "type": [ + "number", + "null" + ] + }, + "final_url": { + "type": [ + "string", + "null" + ] + }, + "bot_protection": { + "type": [ + "boolean", + "null" + ] + }, + "is_reported": { + "type": "boolean" + } + }, + "required": [ + "brand_impersonation", + "has_suspicious_characters", + "is_link_shortener", + "domain_age_days", + "has_email_setup", + "redirect_count", + "final_url", + "bot_protection", + "is_reported" + ], + "additionalProperties": false + }, + "entity_type": { + "type": "string" + }, + "scripts": { + "type": "array", + "items": { + "type": "string" + } + }, + "skeleton": { + "type": "string" + } }, - { - "$ref": "#/components/schemas/VoiceSessionEnded" - } - ], - "discriminator": { - "propertyName": "event" + "required": [ + "match", + "probability", + "span" + ], + "additionalProperties": false } } - } - } - } - }, - "x-webSocket": true, - "operationId": "streamVoice", - "x-speakeasy-ignore": true - } - } - }, - "components": { - "schemas": { - "PublicAuthor": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Author ID in Moderation API" + }, + "required": [ + "label", + "score" + ], + "additionalProperties": false + }, + "description": "The moderation labels of the content." }, - "external_id": { - "description": "The author's ID from your system", + "flagged": { + "type": "boolean", + "description": "Whether moderation flagged the content." + }, + "severity": { + "description": "Severity score from 0 to 1, if available.", "type": [ - "string", + "number", "null" ] }, - "profile_picture": { - "description": "URL of the author's profile picture", - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] + "status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "`pending` waits for review. `resolved` is reviewed." }, - "external_link": { - "description": "URL of the author's external profile", - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "null" - } - ] + "timestamp": { + "type": "number", + "description": "When the content was submitted, as Unix time in milliseconds." }, - "name": { - "description": "Author name or identifier", + "language": { + "description": "Detected ISO language code.", "type": [ "string", "null" ] }, - "email": { - "description": "Author email address", - "anyOf": [ - { - "type": "string", - "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": "null" - } + "conversationId": { + "description": "Conversation ID, if any.", + "type": [ + "string", + "null" ] }, - "company": { - "description": "The author's company or organization", + "authorId": { + "description": "Your author ID, if any.", "type": [ "string", "null" ] }, - "first_seen": { - "type": "number", - "description": "Timestamp when author first appeared" - }, - "last_seen": { - "type": "number", - "description": "Timestamp of last activity" - }, - "last_incident": { - "description": "Timestamp of last incident", + "contentType": { + "description": "Content type, for example `message` or `profile`.", "type": [ - "number", + "string", "null" ] - }, - "status": { - "type": "string", - "enum": [ - "enabled", - "suspended", - "blocked" - ], - "description": "Current author status" - }, - "trust_level": { - "type": "object", - "properties": { - "level": { - "type": "number", - "description": "Author trust level (-1, 0, 1, 2, 3, or 4)" - }, - "manual": { - "type": "boolean", - "description": "True if the trust level was set manually by a moderator" - } - }, - "required": [ - "level", - "manual" - ], - "additionalProperties": false - }, - "block": { - "anyOf": [ - { - "type": "object", - "properties": { - "until": { - "description": "The timestamp until which they are blocked if the author is suspended.", - "type": [ - "number", - "null" - ] - }, - "reason": { - "description": "The moderators reason why the author was blocked or suspended.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "Block or suspension details, if applicable. Null if the author is enabled." - }, - "risk_evaluation": { - "anyOf": [ - { - "type": "object", - "properties": { - "risk_level": { - "description": "Calculated risk level based on more than 10 behavioral signals.", - "anyOf": [ - { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ], - "description": "Risk assessment details, if available." - }, - "metrics": { - "type": "object", - "properties": { - "total_content": { - "type": "number", - "description": "Total pieces of content" - }, - "flagged_content": { - "type": "number", - "description": "Number of flagged content pieces" - }, - "average_sentiment": { - "description": "Average sentiment score of content (-1 to 1). Requires a sentiment model in your project.", - "type": [ - "number", - "null" - ] - } - }, - "required": [ - "total_content", - "flagged_content" - ], - "additionalProperties": false - }, - "metadata": { - "type": "object", - "properties": { - "email_verified": { - "description": "Whether the author's email is verified", - "type": [ - "boolean", - "null" - ] - }, - "phone_verified": { - "description": "Whether the author's phone number 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" - ] - } - }, - "additionalProperties": {}, - "description": "Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process." } }, "required": [ "id", - "first_seen", - "last_seen", + "contentId", + "content", + "labels", + "flagged", + "severity", "status", - "trust_level", - "block", - "risk_evaluation", - "metrics", - "metadata" + "timestamp", + "language", + "conversationId", + "authorId", + "contentType" ], "additionalProperties": false }, @@ -13868,13 +15904,14 @@ "action_not_found", "author_not_found", "queue_not_found", + "queue_item_not_found", "wordlist_not_found", "webhook_not_found", "project_not_found", "channel_not_found" ], "x-speakeasy-unknown-values": "allow", - "description": "Exactly what went wrong. Stable; safe to branch on. New codes may be added.\n\n- `resource_not_found`: The resource does not exist.\n- `action_not_found`: The action does not exist.\n- `author_not_found`: The author does not exist.\n- `queue_not_found`: The review queue does not exist.\n- `wordlist_not_found`: The wordlist does not exist.\n- `webhook_not_found`: The webhook does not exist.\n- `project_not_found`: The project does not exist.\n- `channel_not_found`: The channel does not exist.", + "description": "Exactly what went wrong. Stable; safe to branch on. New codes may be added.\n\n- `resource_not_found`: The resource does not exist.\n- `action_not_found`: The action does not exist.\n- `author_not_found`: The author does not exist.\n- `queue_not_found`: The review queue does not exist.\n- `queue_item_not_found`: The queue item does not exist in the project.\n- `wordlist_not_found`: The wordlist does not exist.\n- `webhook_not_found`: The webhook does not exist.\n- `project_not_found`: The project does not exist.\n- `channel_not_found`: The channel does not exist.", "example": "resource_not_found" }, "message": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b7bd9..a74fce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,104 @@ # Changelog +## 0.5.0 (2026-10-26) + +Full Changelog: [v0.4.0...v0.5.0](https://github.com/moderation-api/cli/compare/v0.4.0...v0.5.0) + +### âš  Breaking changes + +- `GET /authors`: removed the enum value `reportCount` from the `query` request parameter `sortBy` +- `GET /queue/{id}`: the response property `queue/description` became nullable for the status `200` +- `GET /queue/{id}`: removed the required property `queue/filter` from the response with the `200` status +- `GET /queue/{id}/items`: added `subschema #1, subschema #2` to the `data/items/content` response property `anyOf` list for the response status `200` +- `GET /queue/{id}/items`: the response property `data/items/authorId` became nullable for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/contentType` became nullable for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/conversationId` became nullable for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/language` became nullable for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/labels/items/flagged` became optional for the status `200` +- `GET /queue/{id}/items`: the `data/items/content` response's property `type` changed from `string` to `any` for status `200` +- `POST /queue/{id}/items/{itemId}/resolve`: removed the request body +- `POST /queue/{id}/items/{itemId}/unresolve`: removed the request body +- `GET /queue/{id}/stats`: the response property `topReviewers/items/name` became nullable for the status `200` +- `GET /queue/{id}/stats`: the response property `topReviewers/items/userId` became nullable for the status `200` +- `GET /queue/{id}/stats`: removed the required property `actionStats` from the response with the `200` status +- `GET /queue/{id}/stats`: removed the required property `reviewStats/averageTimeToReview` from the response with the `200` status +- `GET /queue/{id}/stats`: removed the required property `topReviewers/items/averageTimePerReview` from the response with the `200` status +- `GET /queue/{id}/stats`: removed the required property `topReviewers/items/topActions` from the response with the `200` status +- `GET /queue/{id}/stats`: removed the required property `trends/flaggedContentTrends` from the response with the `200` status + +### API changes + +- `GET /actions`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /actions/execute`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /actions/{actionId}/execute`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /actions/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /actions/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /actions/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /auth`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /authors`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /authors/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /authors/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /authors/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /channels/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /channels/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /channels/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /channels/{id}/duplicate`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /projects/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /projects/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /projects/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /projects/{id}/api-keys`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /projects/{id}/duplicate`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /projects/{projectId}/channels`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /projects/{projectId}/channels`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /queue/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /queue/{id}/items`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /queue/{id}/stats`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /webhooks/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /webhooks/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /webhooks/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /wordlist`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /wordlist/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `PUT /wordlist/{id}`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /wordlist/{id}/embedding-status`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `DELETE /wordlist/{id}/words`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `POST /wordlist/{id}/words`: added the new `queue_item_not_found` enum value to the `code` response property for the response status `404` +- `GET /authors`: deleted the `query` request parameter `contentTypes` +- `GET /queue/{id}/items`: deleted the `query` request parameter `sortDirection` +- `GET /queue/{id}/items`: deleted the `query` request parameter `sortField` +- a breaking change was detected but the version is still `1.1.0` +- `GET /analytics/authors`: endpoint added +- `GET /analytics/automation`: endpoint added +- `GET /analytics/channels`: endpoint added +- `GET /analytics/policies`: endpoint added +- `GET /analytics/summary`: endpoint added +- `GET /analytics/volumes`: endpoint added +- `POST /feedback`: endpoint added +- `GET /queue`: endpoint added +- `GET /queue/{id}`: the `queue/resolvedItemsCount` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}`: the `queue/totalItemsCount` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}`: the `queue/unresolvedItemsCount` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}`: added the required property `queue/isDefault` to the response with the `200` status +- `GET /queue/{id}/items`: added the new optional `query` request parameter `sort` +- `GET /queue/{id}/items`: added the optional property `data/items/labels/items/manual` to the response with the `200` status +- `GET /queue/{id}/items`: added the optional property `data/items/labels/items/matches` to the response with the `200` status +- `GET /queue/{id}/items`: added the optional property `data/items/labels/items/shadow_flagged` to the response with the `200` status +- `GET /queue/{id}/items`: removed the optional property `data/items/actions` from the response with the `200` status +- `GET /queue/{id}/items`: the response property `data/items/authorId` became required for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/contentType` became required for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/conversationId` became required for the status `200` +- `GET /queue/{id}/items`: the response property `data/items/language` became required for the status `200` +- `GET /queue/{id}/items`: added the required property `data/items/contentId` to the response with the `200` status +- `GET /queue/{id}/items`: added the required property `data/items/severity` to the response with the `200` status +- `POST /queue/{id}/items/resolve`: endpoint added +- `POST /queue/{id}/items/{itemId}/resolve`: removed the optional property `comment` from the response with the `200` status +- `POST /queue/{id}/items/{itemId}/unresolve`: the `status` response property for the response status `200` was restricted to a list of enum values +- `GET /queue/{id}/stats`: removed the optional property `topReviewers/items/accuracyScore` from the response with the `200` status +- `GET /queue/{id}/stats`: the `reviewStats/totalPending` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}/stats`: the `reviewStats/totalReviewed` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}/stats`: the `topReviewers/items/reviewCount` response's property `type` was narrowed from `number` to `integer` for status `200` +- `GET /queue/{id}/stats`: the `trends/dailyReviewCounts/items/count` response's property `type` was narrowed from `number` to `integer` for status `200` +- `POST /wordlist`: endpoint added + ## 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) diff --git a/README.md b/README.md index 0b1ca4d..566ebcf 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ modapi content submit --content '{"type":"text","text":"Hey, check out my profil ## Set up Moderation API in your project -Run `init` in your project's directory. It signs you in if needed, creates a Moderation API project, and writes the project's keys to your env file: +Run `init` in your project's directory. It links one of your Moderation API projects or creates a new one, and writes the project's keys to your env file: ```bash modapi login # once per machine; opens the browser @@ -144,6 +144,8 @@ modapi init --domain acme.com \ --brief "Stop contact details shared outside the platform, scams and harassment" ``` +To connect a directory to a project you already have, run `modapi link` (or `modapi link ""`). It makes a new key for the project, so you can revoke it on its own. Agents and scripts pass `init --project ` or `init --new` instead of answering the question. + `init` does not change your code. To add the moderation calls, let your coding agent do it with the [setup skill](https://github.com/moderation-api/skills): ```bash @@ -362,18 +364,29 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY"
Available commands +* [`analytics`](docs/modapi_analytics.md) - Operations for analytics + * [`summary`](docs/modapi_analytics_summary.md) - Get the analytics summary + * [`policies`](docs/modapi_analytics_policies.md) - Get the most flagged policies + * [`authors`](docs/modapi_analytics_authors.md) - Get the most flagged authors + * [`channels`](docs/modapi_analytics_channels.md) - Get analytics per channel + * [`volumes`](docs/modapi_analytics_volumes.md) - Get volumes over time + * [`automation`](docs/modapi_analytics_automation.md) - Get the automation breakdown * [`authors`](docs/modapi_authors.md) - Operations for authors * [`list`](docs/modapi_authors_list.md) - List authors * [`create`](docs/modapi_authors_create.md) - Create a new author * [`retrieve`](docs/modapi_authors_retrieve.md) - Get author details * [`update`](docs/modapi_authors_update.md) - Update author details * [`delete`](docs/modapi_authors_delete.md) - Delete an author +* [`feedback`](docs/modapi_feedback.md) - Operations for feedback + * [`create`](docs/modapi_feedback_create.md) - Send feedback * [`queue`](docs/modapi_queue.md) - Operations for queue + * [`list`](docs/modapi_queue_list.md) - List queues * [`retrieve`](docs/modapi_queue_retrieve.md) - Get a queue * [`get-stats`](docs/modapi_queue_get-stats.md) - Get queue statistics * [`items`](docs/modapi_queue_items.md) - Operations for items - * [`list`](docs/modapi_queue_items_list.md) - Get queue items + * [`list`](docs/modapi_queue_items_list.md) - List queue items * [`resolve`](docs/modapi_queue_items_resolve.md) - Resolve a queue item + * [`resolve-many`](docs/modapi_queue_items_resolve-many.md) - Resolve queue items * [`unresolve`](docs/modapi_queue_items_unresolve.md) - Unresolve a queue item * [`actions`](docs/modapi_actions.md) - Operations for actions * [`list`](docs/modapi_actions_list.md) - List moderation actions @@ -394,6 +407,7 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY" * [`retrieve`](docs/modapi_account_retrieve.md) - Get account details * [`wordlist`](docs/modapi_wordlist.md) - Operations for wordlist * [`list`](docs/modapi_wordlist_list.md) - List wordlists + * [`create`](docs/modapi_wordlist_create.md) - Create wordlist * [`retrieve`](docs/modapi_wordlist_retrieve.md) - Get wordlist * [`update`](docs/modapi_wordlist_update.md) - Update wordlist * [`get-embedding-status`](docs/modapi_wordlist_get-embedding-status.md) - Get embedding status diff --git a/internal/cli/analytics/authors.go b/internal/cli/analytics/authors.go new file mode 100644 index 0000000..f76c675 --- /dev/null +++ b/internal/cli/analytics/authors.go @@ -0,0 +1,84 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 authorsCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "channel", Shorthand: "c", FieldPath: "Channel", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "limit", Shorthand: "l", FieldPath: "Limit", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 6, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 50, Description: "integer value"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initAuthorsCmd initializes the authors command. +func initAuthorsCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "authors", + Short: "Get the most flagged authors", + Long: "Get the authors with the most flagged content in a time window, largest first.", + Example: " modapi analytics authors", + Args: cobra.NoArgs, + RunE: runAuthorsCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsAuthors", + }, + } + flagutil.RegisterFlags(cmd, authorsCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsAuthorsRequest](authorsCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for authors: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runAuthorsCmd executes the authors command. +func runAuthorsCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsAuthorsRequest](cmd, authorsCmdMeta, "", "") + 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.Analytics.Authors(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/analytics/automation.go b/internal/cli/analytics/automation.go new file mode 100644 index 0000000..b426c02 --- /dev/null +++ b/internal/cli/analytics/automation.go @@ -0,0 +1,83 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 automationCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "channel", Shorthand: "c", FieldPath: "Channel", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initAutomationCmd initializes the automation command. +func initAutomationCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "automation", + Short: "Get the automation breakdown", + Long: "Get how flagged content was handled in a time window: settled by rules or the casebook, or sent to human review, and what the reviewers decided.", + Example: " modapi analytics automation", + Args: cobra.NoArgs, + RunE: runAutomationCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsAutomation", + }, + } + flagutil.RegisterFlags(cmd, automationCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsAutomationRequest](automationCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for automation: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runAutomationCmd executes the automation command. +func runAutomationCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsAutomationRequest](cmd, automationCmdMeta, "", "") + 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.Analytics.Automation(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/analytics/channels.go b/internal/cli/analytics/channels.go new file mode 100644 index 0000000..037b179 --- /dev/null +++ b/internal/cli/analytics/channels.go @@ -0,0 +1,82 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 channelsCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initChannelsCmd initializes the channels command. +func initChannelsCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "channels", + Short: "Get analytics per channel", + Long: "Get requests, flags and flag rate for each channel of the project in a time window.", + Example: " modapi analytics channels", + Args: cobra.NoArgs, + RunE: runChannelsCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsChannels", + }, + } + flagutil.RegisterFlags(cmd, channelsCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsChannelsRequest](channelsCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for channels: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runChannelsCmd executes the channels command. +func runChannelsCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsChannelsRequest](cmd, channelsCmdMeta, "", "") + 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.Analytics.Channels(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/analytics/policies.go b/internal/cli/analytics/policies.go new file mode 100644 index 0000000..1d287ae --- /dev/null +++ b/internal/cli/analytics/policies.go @@ -0,0 +1,84 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 policiesCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "channel", Shorthand: "c", FieldPath: "Channel", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "limit", Shorthand: "l", FieldPath: "Limit", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 8, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 50, Description: "integer value"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initPoliciesCmd initializes the policies command. +func initPoliciesCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "policies", + Short: "Get the most flagged policies", + Long: "Get the policy labels that flagged the most content in a time window, largest first, with the count in the window before.", + Example: " modapi analytics policies", + Args: cobra.NoArgs, + RunE: runPoliciesCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsPolicies", + }, + } + flagutil.RegisterFlags(cmd, policiesCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsPoliciesRequest](policiesCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for policies: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runPoliciesCmd executes the policies command. +func runPoliciesCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsPoliciesRequest](cmd, policiesCmdMeta, "", "") + 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.Analytics.Policies(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/analytics/root.go b/internal/cli/analytics/root.go new file mode 100644 index 0000000..3623f8d --- /dev/null +++ b/internal/cli/analytics/root.go @@ -0,0 +1,51 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +func InitAnalyticsRoot(parent *cobra.Command) error { + var AnalyticsCmd = &cobra.Command{ + Use: "analytics", + Short: "Operations for analytics", + Long: "Operations for analytics", + 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() + }, + } + + if err := initSummaryCmd(AnalyticsCmd); err != nil { + return err + } + + if err := initPoliciesCmd(AnalyticsCmd); err != nil { + return err + } + + if err := initAuthorsCmd(AnalyticsCmd); err != nil { + return err + } + + if err := initChannelsCmd(AnalyticsCmd); err != nil { + return err + } + + if err := initVolumesCmd(AnalyticsCmd); err != nil { + return err + } + + if err := initAutomationCmd(AnalyticsCmd); err != nil { + return err + } + + parent.AddCommand(AnalyticsCmd) + return nil +} diff --git a/internal/cli/analytics/summary.go b/internal/cli/analytics/summary.go new file mode 100644 index 0000000..8262d34 --- /dev/null +++ b/internal/cli/analytics/summary.go @@ -0,0 +1,83 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 summaryCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "channel", Shorthand: "c", FieldPath: "Channel", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initSummaryCmd initializes the summary command. +func initSummaryCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "summary", + Short: "Get the analytics summary", + Long: "Get the totals of the project for a time window: requests, flags, flag rate, allowed, rejected and sent to review, with the window before for the trend. Also the review queue backlog and a flag-rate spike, if there is one.", + Example: " modapi analytics summary", + Args: cobra.NoArgs, + RunE: runSummaryCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsSummary", + }, + } + flagutil.RegisterFlags(cmd, summaryCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsSummaryRequest](summaryCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for summary: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runSummaryCmd executes the summary command. +func runSummaryCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsSummaryRequest](cmd, summaryCmdMeta, "", "") + 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.Analytics.Summary(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/analytics/volumes.go b/internal/cli/analytics/volumes.go new file mode 100644 index 0000000..f228d1b --- /dev/null +++ b/internal/cli/analytics/volumes.go @@ -0,0 +1,83 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package analytics + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 volumesCmdMeta = []flagutil.FlagMeta{ + {FlagName: "range", Shorthand: "r", FieldPath: "Range", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "30d", EnumValues: []string{"24h", "7d", "30d", "90d"}, Description: "The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)"}, + {FlagName: "channel", Shorthand: "c", FieldPath: "Channel", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initVolumesCmd initializes the volumes command. +func initVolumesCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "volumes", + Short: "Get volumes over time", + Long: "Get requests, flags, rejections and reviews per hour (24h) or per day (7d, 30d, 90d), oldest first.", + Example: " modapi analytics volumes", + Args: cobra.NoArgs, + RunE: runVolumesCmd, + Annotations: map[string]string{ + "speakeasy_operation": "getAnalyticsVolumes", + }, + } + flagutil.RegisterFlags(cmd, volumesCmdMeta) + if err := flagutil.ValidateMeta[operations.GetAnalyticsVolumesRequest](volumesCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for volumes: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runVolumesCmd executes the volumes command. +func runVolumesCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + req, err := flagutil.BuildRequest[operations.GetAnalyticsVolumesRequest](cmd, volumesCmdMeta, "", "") + 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.Analytics.Volumes(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/authors/list.go b/internal/cli/authors/list.go index a282bda..b6f2d0e 100644 --- a/internal/cli/authors/list.go +++ b/internal/cli/authors/list.go @@ -16,11 +16,10 @@ import ( var listCmdMeta = []flagutil.FlagMeta{ {FlagName: "page", FieldPath: "Page", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 1, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100000, Description: "integer value"}, {FlagName: "page-size", FieldPath: "PageSize", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 20, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100, Description: "integer value"}, - {FlagName: "sort-by", FieldPath: "SortBy", Kind: flagutil.FlagKindEnum, Optional: true, EnumValues: []string{"trustLevel", "violationCount", "reportCount", "memberSince", "lastActive", "contentCount", "flaggedContentRatio", "averageSentiment"}, Description: "options: trustLevel, violationCount, reportCount, memberSince, lastActive, contentCount, flaggedContentRatio, averageSentiment"}, + {FlagName: "sort-by", FieldPath: "SortBy", Kind: flagutil.FlagKindEnum, Optional: true, EnumValues: []string{"trustLevel", "violationCount", "memberSince", "lastActive", "contentCount", "flaggedContentRatio", "averageSentiment"}, Description: "options: trustLevel, violationCount, memberSince, lastActive, contentCount, flaggedContentRatio, averageSentiment"}, {FlagName: "sort-direction", FieldPath: "SortDirection", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "desc", EnumValues: []string{"asc", "desc"}, Description: "Sort direction (options: asc, desc)"}, {FlagName: "member-since-date", Shorthand: "m", FieldPath: "MemberSinceDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, {FlagName: "last-active-date", Shorthand: "l", FieldPath: "LastActiveDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, - {FlagName: "content-types", Shorthand: "c", FieldPath: "ContentTypes", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "list of values"}, {FlagName: "project-id", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, } @@ -29,7 +28,7 @@ func initListCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "list", Short: "List authors", - Long: "List authors with their activity metrics and reputation. The list is paginated.", + Long: "List the authors of the project, with their status, trust level and content metrics. The list is paginated. By default, the authors that were active most recently come first.", Example: " modapi authors list", Args: cobra.NoArgs, RunE: runListCmd, diff --git a/internal/cli/feedback/create.go b/internal/cli/feedback/create.go new file mode 100644 index 0000000..a1633d2 --- /dev/null +++ b/internal/cli/feedback/create.go @@ -0,0 +1,96 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package feedback + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, + {FlagName: "title", FieldPath: "Body.Title", Kind: flagutil.FlagKindString, Required: true, MinLength: 3, HasMaxLength: true, MaxLength: 120, Description: "A short summary of the request. [required]"}, + {FlagName: "description", FieldPath: "Body.Description", Kind: flagutil.FlagKindString, Required: true, MinLength: 10, HasMaxLength: true, MaxLength: 5000, Description: "What you tried to do, why, and what is missing or does not work. Plain text. Blank lines start new paragraphs. [required]"}, + {FlagName: "type", FieldPath: "Body.Type", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "feature_request", EnumValues: []string{"feature_request", "bug", "other"}, Description: "The kind of feedback. (options: feature_request, bug, other)"}, + {FlagName: "source", Shorthand: "s", FieldPath: "Body.Source", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "api", EnumValues: []string{"mcp", "skill", "cli", "api"}, Description: "Where the feedback was written: MCP server, agent skill, CLI or API. (options: mcp, skill, cli, api)"}, +} + +// initCreateCmd initializes the create command. +func initCreateCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "create", + Short: "Send feedback", + Long: "Sends a feature request, bug report or other feedback to the Moderation API team. The team reads every request and can reply by email. Limited to 5 requests per minute per organization.", + Example: " modapi feedback create --title 'Filter queue items by author trust level' --description 'We review queue items by hand. We want to see items from new authors first, so we need a filter for trust level on the queue items list.'", + Args: cobra.NoArgs, + RunE: runCreateCmd, + Annotations: map[string]string{ + "speakeasy_operation": "createFeedback", + }, + } + flagutil.RegisterFlags(cmd, createCmdMeta) + if err := flagutil.ValidateMeta[operations.CreateFeedbackRequest](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}) + 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(), "createFeedback") + } + req, err := flagutil.BuildRequest[operations.CreateFeedbackRequest](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.Feedback.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/feedback/root.go b/internal/cli/feedback/root.go new file mode 100644 index 0000000..979aad9 --- /dev/null +++ b/internal/cli/feedback/root.go @@ -0,0 +1,31 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package feedback + +import ( + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +func InitFeedbackRoot(parent *cobra.Command) error { + var FeedbackCmd = &cobra.Command{ + Use: "feedback", + Short: "Operations for feedback", + Long: "Operations for feedback", + 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() + }, + } + + if err := initCreateCmd(FeedbackCmd); err != nil { + return err + } + + parent.AddCommand(FeedbackCmd) + return nil +} diff --git a/internal/cli/queue/getstats.go b/internal/cli/queue/getstats.go index 4d36788..552e007 100644 --- a/internal/cli/queue/getstats.go +++ b/internal/cli/queue/getstats.go @@ -15,7 +15,7 @@ import ( ) var getStatsCmdMeta = []flagutil.FlagMeta{ - {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID [required]"}, + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, {FlagName: "within-days", Shorthand: "w", FieldPath: "WithinDays", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 30, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 365, Description: "integer value"}, {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, } @@ -25,7 +25,7 @@ func initGetStatsCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "get-stats [id]", Short: "Get queue statistics", - Long: "Get detailed statistics about a moderation queue including review times, action counts, and trends", + Long: "Get review statistics of a queue for a period: resolved and pending items, the top reviewers and the resolved items per day.", Example: " modapi queue get-stats --id ", Args: flagutil.PositionalFlagArgs, RunE: runGetStatsCmd, @@ -38,11 +38,11 @@ func initGetStatsCmd(parent *cobra.Command) error { if err := flagutil.ValidateMeta[operations.GetQueueStatsRequest](getStatsCmdMeta); err != nil { return fmt.Errorf("invalid metadata for get-stats: %w", err) } - if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID (or pass it as the [id] argument)", true); err != nil { + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID. (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 queue ID", Required: true, SatisfiedBy: []string{"id"}}, + {Name: "id", Summary: "The queue ID.", Required: true, SatisfiedBy: []string{"id"}}, }}); err != nil { return fmt.Errorf("declare interactive arguments for get-stats: %w", err) } diff --git a/internal/cli/queue/items/list.go b/internal/cli/queue/items/list.go index a4974b5..5890aec 100644 --- a/internal/cli/queue/items/list.go +++ b/internal/cli/queue/items/list.go @@ -15,18 +15,17 @@ import ( ) var listCmdMeta = []flagutil.FlagMeta{ - {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID [required]"}, + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, {FlagName: "page", FieldPath: "Page", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 1, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100000, Description: "integer value"}, {FlagName: "page-size", FieldPath: "PageSize", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 20, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100, Description: "integer value"}, - {FlagName: "sort-field", FieldPath: "SortField", Kind: flagutil.FlagKindEnum, Optional: true, EnumValues: []string{"createdAt", "severity", "reviewedAt"}, Description: "options: createdAt, severity, reviewedAt"}, - {FlagName: "sort-direction", FieldPath: "SortDirection", Kind: flagutil.FlagKindEnum, Optional: true, HasDefault: true, DefaultStr: "desc", EnumValues: []string{"asc", "desc"}, Description: "Sort direction (options: asc, desc)"}, + {FlagName: "sort", Shorthand: "s", FieldPath: "Sort", Kind: flagutil.FlagKindEnum, Optional: true, EnumValues: []string{"FIFO", "LIFO", "RANDOM", "SEVERITY_DESC"}, Description: "options: FIFO, LIFO, RANDOM, SEVERITY_DESC"}, + {FlagName: "include-resolved", FieldPath: "IncludeResolved", Kind: flagutil.FlagKindBool, Optional: true, Description: "boolean flag"}, {FlagName: "conversation-ids", Shorthand: "c", FieldPath: "ConversationIds", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "list of values"}, {FlagName: "labels", Shorthand: "l", FieldPath: "Labels", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "list of values"}, - {FlagName: "after-date", FieldPath: "AfterDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, - {FlagName: "before-date", Shorthand: "b", FieldPath: "BeforeDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, - {FlagName: "include-resolved", FieldPath: "IncludeResolved", Kind: flagutil.FlagKindBool, Optional: true, Description: "boolean flag"}, {FlagName: "author-id", FieldPath: "AuthorID", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, {FlagName: "filtered-action-ids", Shorthand: "f", FieldPath: "FilteredActionIds", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "list of values"}, + {FlagName: "after-date", FieldPath: "AfterDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, + {FlagName: "before-date", Shorthand: "b", FieldPath: "BeforeDate", Kind: flagutil.FlagKindString, Optional: true, Description: "string value"}, {FlagName: "project-id", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, } @@ -34,8 +33,8 @@ var listCmdMeta = []flagutil.FlagMeta{ func initListCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "list [id]", - Short: "Get queue items", - Long: "Get paginated list of items in a moderation queue with filtering options", + Short: "List queue items", + Long: "List the items of a review queue, with the filters and the date window of the queue applied. By default only pending items. The list is paginated. Use the item `id` to resolve or unresolve an item.", Example: " modapi queue items list --id ", Args: flagutil.PositionalFlagArgs, RunE: runListCmd, @@ -49,11 +48,11 @@ func initListCmd(parent *cobra.Command) error { } cmd.Flags().BoolP("all", "a", false, "Automatically paginate and fetch all results (streams NDJSON for JSON output)") cmd.Flags().Int("max-pages", 0, "Maximum number of pages to fetch when using --all (0 = no limit)") - if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID (or pass it as the [id] argument)", true); err != nil { + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID. (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 queue ID", Required: true, SatisfiedBy: []string{"id"}}, + {Name: "id", Summary: "The queue ID.", Required: true, SatisfiedBy: []string{"id"}}, }}); err != nil { return fmt.Errorf("declare interactive arguments for list: %w", err) } diff --git a/internal/cli/queue/items/resolve.go b/internal/cli/queue/items/resolve.go index b3dbc24..32ada46 100644 --- a/internal/cli/queue/items/resolve.go +++ b/internal/cli/queue/items/resolve.go @@ -14,10 +14,9 @@ import ( ) var resolveCmdMeta = []flagutil.FlagMeta{ - {FlagName: "id", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID [required]"}, - {FlagName: "item-id", FieldPath: "ItemID", Kind: flagutil.FlagKindString, Required: true, Description: "The item ID to resolve [required]"}, + {FlagName: "id", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, + {FlagName: "item-id", FieldPath: "ItemID", Kind: flagutil.FlagKindString, Required: true, Description: "The item ID to resolve. [required]"}, {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, - {FlagName: "comment", Shorthand: "c", FieldPath: "Body.Comment", Kind: flagutil.FlagKindString, Optional: true, Description: "Optional comment"}, } // initResolveCmd initializes the resolve command. @@ -25,7 +24,7 @@ func initResolveCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "resolve", Short: "Resolve a queue item", - Long: "Mark a queue item as resolved with a specific moderation action", + Long: "Mark an item of a queue as resolved. It is the same as a check-off in the dashboard: the item leaves the pending list and a QUEUE_ITEM_COMPLETED webhook is sent. To also take a moderation action, use `POST /actions/execute`.", Example: " modapi queue items resolve --id --item-id ", Args: cobra.NoArgs, RunE: runResolveCmd, @@ -37,14 +36,6 @@ func initResolveCmd(parent *cobra.Command) error { if err := flagutil.ValidateMeta[operations.ResolveQueueItemRequest](resolveCmdMeta); err != nil { return fmt.Errorf("invalid metadata for resolve: %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, resolveCmdMeta, "Body", "body"); err != nil { - return fmt.Errorf("annotate body fields for resolve: %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}) parent.AddCommand(cmd) return nil } @@ -54,10 +45,7 @@ func runResolveCmd(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(), "resolveQueueItem") - } - req, err := flagutil.BuildRequest[operations.ResolveQueueItemRequest](cmd, resolveCmdMeta, "Body", "body") + req, err := flagutil.BuildRequest[operations.ResolveQueueItemRequest](cmd, resolveCmdMeta, "", "") if err != nil { return flagutil.WithCLIValidation(err) } diff --git a/internal/cli/queue/items/resolvemany.go b/internal/cli/queue/items/resolvemany.go new file mode 100644 index 0000000..ec1a83b --- /dev/null +++ b/internal/cli/queue/items/resolvemany.go @@ -0,0 +1,107 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package items + +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 resolveManyCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, + {FlagName: "item-ids", FieldPath: "Body.ItemIds", Kind: flagutil.FlagKindStringArray, Required: true, Description: "The item IDs to resolve. Up to 100. [required]"}, +} + +// initResolveManyCmd initializes the resolve-many command. +func initResolveManyCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "resolve-many [id]", + Short: "Resolve queue items", + Long: "Mark up to 100 items of a queue as resolved in one request. Each item is resolved the same as with `POST /queue/{id}/items/{itemId}/resolve`. IDs that do not exist in the project are skipped and returned in `notFoundIds`.", + Example: " modapi queue items resolve-many --id --item-ids clx8k2v0e000108l4a9bz3f7q --item-ids clx8k2v0e000208l4h2mc6d1r", + Args: flagutil.PositionalFlagArgs, + RunE: runResolveManyCmd, + Aliases: []string{"rm"}, + Annotations: map[string]string{ + "speakeasy_operation": "resolveQueueItems", + }, + } + flagutil.RegisterFlags(cmd, resolveManyCmdMeta) + if err := flagutil.ValidateMeta[operations.ResolveQueueItemsRequest](resolveManyCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for resolve-many: %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, resolveManyCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for resolve-many: %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 queue ID. (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 queue ID.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for resolve-many: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runResolveManyCmd executes the resolve-many command. +func runResolveManyCmd(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(), "resolveQueueItems") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.ResolveQueueItemsRequest](cmd, resolveManyCmdMeta, "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.Queue.Items.ResolveMany(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/queue/items/root.go b/internal/cli/queue/items/root.go index bb994a9..ab704cd 100644 --- a/internal/cli/queue/items/root.go +++ b/internal/cli/queue/items/root.go @@ -30,6 +30,10 @@ func InitItemsRoot(parent *cobra.Command) error { return err } + if err := initResolveManyCmd(ItemsCmd); err != nil { + return err + } + if err := initUnresolveCmd(ItemsCmd); err != nil { return err } diff --git a/internal/cli/queue/items/unresolve.go b/internal/cli/queue/items/unresolve.go index 53d9817..77f64f6 100644 --- a/internal/cli/queue/items/unresolve.go +++ b/internal/cli/queue/items/unresolve.go @@ -14,10 +14,9 @@ import ( ) var unresolveCmdMeta = []flagutil.FlagMeta{ - {FlagName: "id", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID [required]"}, - {FlagName: "item-id", FieldPath: "ItemID", Kind: flagutil.FlagKindString, Required: true, Description: "The item ID to unresolve [required]"}, + {FlagName: "id", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, + {FlagName: "item-id", FieldPath: "ItemID", Kind: flagutil.FlagKindString, Required: true, Description: "The item ID to unresolve. [required]"}, {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, - {FlagName: "comment", Shorthand: "c", FieldPath: "Body.Comment", Kind: flagutil.FlagKindString, Optional: true, Description: "Optional reason for unresolving the item"}, } // initUnresolveCmd initializes the unresolve command. @@ -25,7 +24,7 @@ func initUnresolveCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "unresolve", Short: "Unresolve a queue item", - Long: "Mark a previously resolved queue item as unresolved/pending", + Long: "Mark a resolved item of a queue as pending again. It is the same as removing a check-off in the dashboard: the item comes back in the pending list.", Example: " modapi queue items unresolve --id --item-id ", Args: cobra.NoArgs, RunE: runUnresolveCmd, @@ -37,14 +36,6 @@ func initUnresolveCmd(parent *cobra.Command) error { if err := flagutil.ValidateMeta[operations.UnresolveQueueItemRequest](unresolveCmdMeta); err != nil { return fmt.Errorf("invalid metadata for unresolve: %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, unresolveCmdMeta, "Body", "body"); err != nil { - return fmt.Errorf("annotate body fields for unresolve: %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}) parent.AddCommand(cmd) return nil } @@ -54,10 +45,7 @@ func runUnresolveCmd(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(), "unresolveQueueItem") - } - req, err := flagutil.BuildRequest[operations.UnresolveQueueItemRequest](cmd, unresolveCmdMeta, "Body", "body") + req, err := flagutil.BuildRequest[operations.UnresolveQueueItemRequest](cmd, unresolveCmdMeta, "", "") if err != nil { return flagutil.WithCLIValidation(err) } diff --git a/internal/cli/queue/list.go b/internal/cli/queue/list.go new file mode 100644 index 0000000..d5625e2 --- /dev/null +++ b/internal/cli/queue/list.go @@ -0,0 +1,107 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package queue + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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 listCmdMeta = []flagutil.FlagMeta{ + {FlagName: "page", FieldPath: "Page", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 1, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100000, Description: "integer value"}, + {FlagName: "page-size", FieldPath: "PageSize", Kind: flagutil.FlagKindInt64, Optional: true, HasDefault: true, DefaultInt: 20, HasMinimum: true, Minimum: 1, HasMaximum: true, Maximum: 100, Description: "integer value"}, + {FlagName: "project-id", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, +} + +// initListCmd initializes the list command. +func initListCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "list", + Short: "List queues", + Long: "List the review queues of the project. The default queue comes first. The list is paginated. To get the item counts of a queue, use `GET /queue/{id}`.", + Example: " modapi queue list", + Args: cobra.NoArgs, + RunE: runListCmd, + Annotations: map[string]string{ + "speakeasy_operation": "listQueues", + }, + } + flagutil.RegisterFlags(cmd, listCmdMeta) + if err := flagutil.ValidateMeta[operations.ListQueuesRequest](listCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for list: %w", err) + } + cmd.Flags().BoolP("all", "a", false, "Automatically paginate and fetch all results (streams NDJSON for JSON output)") + cmd.Flags().Int("max-pages", 0, "Maximum number of pages to fetch when using --all (0 = no limit)") + parent.AddCommand(cmd) + return nil +} + +// runListCmd executes the list command. +func runListCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + allPages, _ := flagutil.GetBoolFlag(cmd, "all") + maxPages, _ := flagutil.GetIntFlag(cmd, "max-pages") + if maxPages < 0 { + return flagutil.WithCLIValidation(fmt.Errorf("--max-pages must be zero or greater")) + } + if flagutil.FlagChanged(cmd, "max-pages") && !allPages { + return flagutil.WithCLIValidation(fmt.Errorf("--max-pages requires --all")) + } + req, err := flagutil.BuildRequest[operations.ListQueuesRequest](cmd, listCmdMeta, "", "") + 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 allPages && !client.IsDryRun(cmd) { + res, err := s.Queue.List(cmd.Context(), req, sdkOpts...) + if err != nil { + return output.Error(cmd, err) + } + return output.PaginatedResult(cmd, res, "Object", "Data", maxPages, output.PaginationProbe{ + Type: "offsetLimit", + CursorKind: "", + NextCursor: "", + NextURL: "", + Results: "$.data", + HasLimit: true, + }) + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Queue.List(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/queue/retrieve.go b/internal/cli/queue/retrieve.go index 672d766..bb2e656 100644 --- a/internal/cli/queue/retrieve.go +++ b/internal/cli/queue/retrieve.go @@ -15,7 +15,7 @@ import ( ) var retrieveCmdMeta = []flagutil.FlagMeta{ - {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID [required]"}, + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The queue ID. [required]"}, {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, } @@ -24,7 +24,7 @@ func initRetrieveCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "retrieve [id]", Short: "Get a queue", - Long: "Get a queue", + Long: "Get a review queue with its item counts. The counts use the filters and the date window of the queue, the same as the dashboard.", Example: " modapi queue retrieve --id ", Args: flagutil.PositionalFlagArgs, RunE: runRetrieveCmd, @@ -36,11 +36,11 @@ func initRetrieveCmd(parent *cobra.Command) error { if err := flagutil.ValidateMeta[operations.GetQueueRequest](retrieveCmdMeta); err != nil { return fmt.Errorf("invalid metadata for retrieve: %w", err) } - if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID (or pass it as the [id] argument)", true); err != nil { + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The queue ID. (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 queue ID", Required: true, SatisfiedBy: []string{"id"}}, + {Name: "id", Summary: "The queue ID.", Required: true, SatisfiedBy: []string{"id"}}, }}); err != nil { return fmt.Errorf("declare interactive arguments for retrieve: %w", err) } diff --git a/internal/cli/queue/root.go b/internal/cli/queue/root.go index 35e7d51..41eba28 100644 --- a/internal/cli/queue/root.go +++ b/internal/cli/queue/root.go @@ -27,6 +27,10 @@ func InitQueueRoot(parent *cobra.Command) error { return err } + if err := initListCmd(QueueCmd); err != nil { + return err + } + if err := initRetrieveCmd(QueueCmd); err != nil { return err } diff --git a/internal/cli/root.go b/internal/cli/root.go index 57d75ed..16518de 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -7,9 +7,11 @@ import ( "fmt" "github.com/moderation-api/cli/internal/cli/account" "github.com/moderation-api/cli/internal/cli/actions" + "github.com/moderation-api/cli/internal/cli/analytics" "github.com/moderation-api/cli/internal/cli/authors" "github.com/moderation-api/cli/internal/cli/channels" "github.com/moderation-api/cli/internal/cli/content" + "github.com/moderation-api/cli/internal/cli/feedback" "github.com/moderation-api/cli/internal/cli/projects" "github.com/moderation-api/cli/internal/cli/queue" "github.com/moderation-api/cli/internal/cli/webhooks" @@ -83,9 +85,15 @@ func NewRootCommand() (*cobra.Command, error) { return nil }, } + if err := analytics.InitAnalyticsRoot(rootCmd); err != nil { + return nil, fmt.Errorf("init analytics: %w", err) + } if err := authors.InitAuthorsRoot(rootCmd); err != nil { return nil, fmt.Errorf("init authors: %w", err) } + if err := feedback.InitFeedbackRoot(rootCmd); err != nil { + return nil, fmt.Errorf("init feedback: %w", err) + } if err := queue.InitQueueRoot(rootCmd); err != nil { return nil, fmt.Errorf("init queue: %w", err) } diff --git a/internal/cli/version.go b/internal/cli/version.go index 5d086d0..35c3cf4 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.4.0" +var Version = "0.5.0" // BuildTime is optionally set at build time via ldflags targeting the main package. var BuildTime string diff --git a/internal/cli/wordlist/create.go b/internal/cli/wordlist/create.go new file mode 100644 index 0000000..6b9ae8b --- /dev/null +++ b/internal/cli/wordlist/create.go @@ -0,0 +1,96 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package wordlist + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "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: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "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."}, + {FlagName: "name", Shorthand: "n", FieldPath: "Body.Name", Kind: flagutil.FlagKindString, Required: true, MinLength: 1, HasMaxLength: true, MaxLength: 100, Description: "Name of the wordlist [required]"}, + {FlagName: "key", Shorthand: "k", FieldPath: "Body.Key", Kind: flagutil.FlagKindString, Optional: true, Description: "Key of the wordlist. Default: made from the name. If the key is already in use, a suffix is added."}, + {FlagName: "description", FieldPath: "Body.Description", Kind: flagutil.FlagKindString, Optional: true, Description: "Description of the wordlist"}, + {FlagName: "words", Shorthand: "w", FieldPath: "Body.Words", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "Words to add to the wordlist. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words."}, +} + +// initCreateCmd initializes the create command. +func initCreateCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "create", + Short: "Create wordlist", + Long: "Create a wordlist in the organization. You can add words now or later. Each word is embedded in the background for similarity matching. Use the embedding status endpoint to follow the progress. To use the wordlist in moderation, add it to a policy.", + Example: " modapi wordlist create --name ", + Args: cobra.NoArgs, + RunE: runCreateCmd, + Annotations: map[string]string{ + "speakeasy_operation": "createWordlist", + }, + } + flagutil.RegisterFlags(cmd, createCmdMeta) + if err := flagutil.ValidateMeta[operations.CreateWordlistRequest](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}) + 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(), "createWordlist") + } + req, err := flagutil.BuildRequest[operations.CreateWordlistRequest](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.Wordlist.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/wordlist/root.go b/internal/cli/wordlist/root.go index 0dc0951..61a0cc3 100644 --- a/internal/cli/wordlist/root.go +++ b/internal/cli/wordlist/root.go @@ -31,6 +31,10 @@ func InitWordlistRoot(parent *cobra.Command) error { return err } + if err := initCreateCmd(WordlistCmd); err != nil { + return err + } + if err := initRetrieveCmd(WordlistCmd); err != nil { return err } diff --git a/internal/cli/wordlist/update.go b/internal/cli/wordlist/update.go index c222b35..79791fd 100644 --- a/internal/cli/wordlist/update.go +++ b/internal/cli/wordlist/update.go @@ -20,7 +20,7 @@ var updateCmdMeta = []flagutil.FlagMeta{ {FlagName: "name", Shorthand: "n", FieldPath: "Body.Name", Kind: flagutil.FlagKindString, Optional: true, Description: "New name for the wordlist"}, {FlagName: "key", Shorthand: "k", FieldPath: "Body.Key", Kind: flagutil.FlagKindString, Optional: true, Description: "New key for the wordlist"}, {FlagName: "description", FieldPath: "Body.Description", Kind: flagutil.FlagKindString, Optional: true, Description: "New description for the wordlist"}, - {FlagName: "words", Shorthand: "w", FieldPath: "Body.Words", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored."}, + {FlagName: "words", Shorthand: "w", FieldPath: "Body.Words", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "New words for the wordlist. Replace the existing words with these new ones. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words."}, {FlagName: "strict", Shorthand: "s", FieldPath: "Body.Strict", Kind: flagutil.FlagKindBool, Optional: true, Description: "Deprecated. Now using threshold in project settings."}, } @@ -29,7 +29,7 @@ func initUpdateCmd(parent *cobra.Command) error { var cmd = &cobra.Command{ Use: "update [id]", Short: "Update wordlist", - Long: "Update a wordlist", + Long: "Update a wordlist. Fields that you do not send stay the same. If you send words, they replace all existing words, and the embeddings are updated in the background.", Example: " modapi wordlist update --id ", Args: flagutil.PositionalFlagArgs, RunE: runUpdateCmd, diff --git a/internal/sdk/analytics.go b/internal/sdk/analytics.go new file mode 100644 index 0000000..76253e0 --- /dev/null +++ b/internal/sdk/analytics.go @@ -0,0 +1,1897 @@ +// 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" + "net/url" +) + +type Analytics struct { + rootSDK *ModerationAPI + sdkConfiguration config.SDKConfiguration + hooks *hooks.Hooks +} + +func newAnalytics(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks *hooks.Hooks) *Analytics { + return &Analytics{ + rootSDK: rootSDK, + sdkConfiguration: sdkConfig, + hooks: hooks, + } +} + +// Summary - Get the analytics summary +// Get the totals of the project for a time window: requests, flags, flag rate, allowed, rejected and sent to review, with the window before for the trend. Also the review queue backlog and a flag-rate spike, if there is one. +func (s *Analytics) Summary(ctx context.Context, request *operations.GetAnalyticsSummaryRequest, opts ...operations.Option) (*operations.GetAnalyticsSummaryResponse, 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 := url.JoinPath(baseURL, "/analytics/summary") + 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: "getAnalyticsSummary", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsSummaryResponse{ + 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 operations.GetAnalyticsSummaryResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + +// Policies - Get the most flagged policies +// Get the policy labels that flagged the most content in a time window, largest first, with the count in the window before. +func (s *Analytics) Policies(ctx context.Context, request *operations.GetAnalyticsPoliciesRequest, opts ...operations.Option) (*operations.GetAnalyticsPoliciesResponse, 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 := url.JoinPath(baseURL, "/analytics/policies") + 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: "getAnalyticsPolicies", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsPoliciesResponse{ + 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 operations.GetAnalyticsPoliciesResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + +// Authors - Get the most flagged authors +// Get the authors with the most flagged content in a time window, largest first. +func (s *Analytics) Authors(ctx context.Context, request *operations.GetAnalyticsAuthorsRequest, opts ...operations.Option) (*operations.GetAnalyticsAuthorsResponse, 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 := url.JoinPath(baseURL, "/analytics/authors") + 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: "getAnalyticsAuthors", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsAuthorsResponse{ + 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 operations.GetAnalyticsAuthorsResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + +// Channels - Get analytics per channel +// Get requests, flags and flag rate for each channel of the project in a time window. +func (s *Analytics) Channels(ctx context.Context, request *operations.GetAnalyticsChannelsRequest, opts ...operations.Option) (*operations.GetAnalyticsChannelsResponse, 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 := url.JoinPath(baseURL, "/analytics/channels") + 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: "getAnalyticsChannels", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsChannelsResponse{ + 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 operations.GetAnalyticsChannelsResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + +// Volumes - Get volumes over time +// Get requests, flags, rejections and reviews per hour (24h) or per day (7d, 30d, 90d), oldest first. +func (s *Analytics) Volumes(ctx context.Context, request *operations.GetAnalyticsVolumesRequest, opts ...operations.Option) (*operations.GetAnalyticsVolumesResponse, 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 := url.JoinPath(baseURL, "/analytics/volumes") + 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: "getAnalyticsVolumes", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsVolumesResponse{ + 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 operations.GetAnalyticsVolumesResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + +// Automation - Get the automation breakdown +// Get how flagged content was handled in a time window: settled by rules or the casebook, or sent to human review, and what the reviewers decided. +func (s *Analytics) Automation(ctx context.Context, request *operations.GetAnalyticsAutomationRequest, opts ...operations.Option) (*operations.GetAnalyticsAutomationResponse, 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 := url.JoinPath(baseURL, "/analytics/automation") + 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: "getAnalyticsAutomation", + SecuritySource: s.sdkConfiguration.Security, + } + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.GetAnalyticsAutomationResponse{ + 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 operations.GetAnalyticsAutomationResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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/authors.go b/internal/sdk/authors.go index 23da930..9a2dac7 100644 --- a/internal/sdk/authors.go +++ b/internal/sdk/authors.go @@ -32,7 +32,7 @@ func newAuthors(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks } // List authors -// List authors with their activity metrics and reputation. The list is paginated. +// List the authors of the project, with their status, trust level and content metrics. The list is paginated. By default, the authors that were active most recently come first. func (s *Authors) List(ctx context.Context, request *operations.ListAuthorsRequest, opts ...operations.Option) (*operations.ListAuthorsResponse, error) { o := operations.Options{} supportedOptions := []string{ diff --git a/internal/sdk/feedback.go b/internal/sdk/feedback.go new file mode 100644 index 0000000..ddc8b60 --- /dev/null +++ b/internal/sdk/feedback.go @@ -0,0 +1,270 @@ +// 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" + "net/url" +) + +type Feedback struct { + rootSDK *ModerationAPI + sdkConfiguration config.SDKConfiguration + hooks *hooks.Hooks +} + +func newFeedback(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks *hooks.Hooks) *Feedback { + return &Feedback{ + rootSDK: rootSDK, + sdkConfiguration: sdkConfig, + hooks: hooks, + } +} + +// Create - Send feedback +// Sends a feature request, bug report or other feedback to the Moderation API team. The team reads every request and can reply by email. Limited to 5 requests per minute per organization. +func (s *Feedback) Create(ctx context.Context, request operations.CreateFeedbackRequest, opts ...operations.Option) (*operations.CreateFeedbackResponse, 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 := url.JoinPath(baseURL, "/feedback") + 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: "createFeedback", + 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) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + 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.CreateFeedbackResponse{ + 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 operations.CreateFeedbackResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 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/items.go b/internal/sdk/items.go index 2aa0e3f..2f3d207 100644 --- a/internal/sdk/items.go +++ b/internal/sdk/items.go @@ -30,8 +30,8 @@ func newItems(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks * } } -// List - Get queue items -// Get paginated list of items in a moderation queue with filtering options +// List queue items +// List the items of a review queue, with the filters and the date window of the queue applied. By default only pending items. The list is paginated. Use the item `id` to resolve or unresolve an item. func (s *Items) List(ctx context.Context, request operations.ListQueueItemsRequest, opts ...operations.Option) (*operations.ListQueueItemsResponse, error) { o := operations.Options{} supportedOptions := []string{ @@ -404,7 +404,7 @@ func (s *Items) List(ctx context.Context, request operations.ListQueueItemsReque } // Resolve a queue item -// Mark a queue item as resolved with a specific moderation action +// Mark an item of a queue as resolved. It is the same as a check-off in the dashboard: the item leaves the pending list and a QUEUE_ITEM_COMPLETED webhook is sent. To also take a moderation action, use `POST /actions/execute`. func (s *Items) Resolve(ctx context.Context, request operations.ResolveQueueItemRequest, opts ...operations.Option) (*operations.ResolveQueueItemResponse, error) { o := operations.Options{} supportedOptions := []string{ @@ -437,6 +437,288 @@ func (s *Items) Resolve(ctx context.Context, request operations.ResolveQueueItem OperationID: "resolveQueueItem", SecuritySource: s.sdkConfiguration.Security, } + + 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, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + 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.ResolveQueueItemResponse{ + 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 operations.ResolveQueueItemResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 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 == 500: + 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.InternalServerError + 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 + +} + +// ResolveMany - Resolve queue items +// Mark up to 100 items of a queue as resolved in one request. Each item is resolved the same as with `POST /queue/{id}/items/{itemId}/resolve`. IDs that do not exist in the project are skipped and returned in `notFoundIds`. +func (s *Items) ResolveMany(ctx context.Context, request operations.ResolveQueueItemsRequest, opts ...operations.Option) (*operations.ResolveQueueItemsResponse, 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, "/queue/{id}/items/resolve", 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: "resolveQueueItems", + 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 @@ -509,7 +791,7 @@ func (s *Items) Resolve(ctx context.Context, request operations.ResolveQueueItem } } - res := &operations.ResolveQueueItemResponse{ + res := &operations.ResolveQueueItemsResponse{ HTTPMeta: components.HTTPMetadata{ Request: req, Response: httpRes, @@ -529,7 +811,7 @@ func (s *Items) Resolve(ctx context.Context, request operations.ResolveQueueItem return nil, err } - var out operations.ResolveQueueItemResponseBody + var out operations.ResolveQueueItemsResponseBody if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { return nil, err } @@ -693,7 +975,7 @@ func (s *Items) Resolve(ctx context.Context, request operations.ResolveQueueItem } // Unresolve a queue item -// Mark a previously resolved queue item as unresolved/pending +// Mark a resolved item of a queue as pending again. It is the same as removing a check-off in the dashboard: the item comes back in the pending list. func (s *Items) Unresolve(ctx context.Context, request operations.UnresolveQueueItemRequest, opts ...operations.Option) (*operations.UnresolveQueueItemResponse, error) { o := operations.Options{} supportedOptions := []string{ @@ -726,10 +1008,6 @@ func (s *Items) Unresolve(ctx context.Context, request operations.UnresolveQueue OperationID: "unresolveQueueItem", 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 { @@ -749,15 +1027,12 @@ func (s *Items) Unresolve(ctx context.Context, request operations.UnresolveQueue }() } - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + req, err := http.NewRequestWithContext(ctx, "POST", opURL, nil) 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) - } utils.PopulateHeaders(ctx, req, request, nil) diff --git a/internal/sdk/models/components/notfounderror.go b/internal/sdk/models/components/notfounderror.go index 0ac1689..b67c32c 100644 --- a/internal/sdk/models/components/notfounderror.go +++ b/internal/sdk/models/components/notfounderror.go @@ -8,6 +8,7 @@ package components // - `action_not_found`: The action does not exist. // - `author_not_found`: The author does not exist. // - `queue_not_found`: The review queue does not exist. +// - `queue_item_not_found`: The queue item does not exist in the project. // - `wordlist_not_found`: The wordlist does not exist. // - `webhook_not_found`: The webhook does not exist. // - `project_not_found`: The project does not exist. @@ -15,14 +16,15 @@ package components type NotFoundErrorCode string const ( - NotFoundErrorCodeResourceNotFound NotFoundErrorCode = "resource_not_found" - NotFoundErrorCodeActionNotFound NotFoundErrorCode = "action_not_found" - NotFoundErrorCodeAuthorNotFound NotFoundErrorCode = "author_not_found" - NotFoundErrorCodeQueueNotFound NotFoundErrorCode = "queue_not_found" - NotFoundErrorCodeWordlistNotFound NotFoundErrorCode = "wordlist_not_found" - NotFoundErrorCodeWebhookNotFound NotFoundErrorCode = "webhook_not_found" - NotFoundErrorCodeProjectNotFound NotFoundErrorCode = "project_not_found" - NotFoundErrorCodeChannelNotFound NotFoundErrorCode = "channel_not_found" + NotFoundErrorCodeResourceNotFound NotFoundErrorCode = "resource_not_found" + NotFoundErrorCodeActionNotFound NotFoundErrorCode = "action_not_found" + NotFoundErrorCodeAuthorNotFound NotFoundErrorCode = "author_not_found" + NotFoundErrorCodeQueueNotFound NotFoundErrorCode = "queue_not_found" + NotFoundErrorCodeQueueItemNotFound NotFoundErrorCode = "queue_item_not_found" + NotFoundErrorCodeWordlistNotFound NotFoundErrorCode = "wordlist_not_found" + NotFoundErrorCodeWebhookNotFound NotFoundErrorCode = "webhook_not_found" + NotFoundErrorCodeProjectNotFound NotFoundErrorCode = "project_not_found" + NotFoundErrorCodeChannelNotFound NotFoundErrorCode = "channel_not_found" ) func (e NotFoundErrorCode) ToPointer() *NotFoundErrorCode { @@ -33,7 +35,7 @@ func (e NotFoundErrorCode) ToPointer() *NotFoundErrorCode { func (e *NotFoundErrorCode) IsExact() bool { if e != nil { switch *e { - case "resource_not_found", "action_not_found", "author_not_found", "queue_not_found", "wordlist_not_found", "webhook_not_found", "project_not_found", "channel_not_found": + case "resource_not_found", "action_not_found", "author_not_found", "queue_not_found", "queue_item_not_found", "wordlist_not_found", "webhook_not_found", "project_not_found", "channel_not_found": return true } } diff --git a/internal/sdk/models/components/publicauthor.go b/internal/sdk/models/components/publicauthor.go index b726a12..34ec6e1 100644 --- a/internal/sdk/models/components/publicauthor.go +++ b/internal/sdk/models/components/publicauthor.go @@ -7,21 +7,21 @@ import ( "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" ) -// Status - Current author status -type Status string +// PublicAuthorStatus - Current author status +type PublicAuthorStatus string const ( - StatusEnabled Status = "enabled" - StatusSuspended Status = "suspended" - StatusBlocked Status = "blocked" + PublicAuthorStatusEnabled PublicAuthorStatus = "enabled" + PublicAuthorStatusSuspended PublicAuthorStatus = "suspended" + PublicAuthorStatusBlocked PublicAuthorStatus = "blocked" ) -func (e Status) ToPointer() *Status { +func (e PublicAuthorStatus) ToPointer() *PublicAuthorStatus { return &e } // IsExact returns true if the value matches a known enum value, false otherwise. -func (e *Status) IsExact() bool { +func (e *PublicAuthorStatus) IsExact() bool { if e != nil { switch *e { case "enabled", "suspended", "blocked": @@ -196,8 +196,8 @@ type PublicAuthor struct { // Timestamp of last incident LastIncident optionalnullable.OptionalNullable[float64] `json:"last_incident,omitzero"` // Current author status - Status Status `json:"status"` - TrustLevel TrustLevel `json:"trust_level"` + Status PublicAuthorStatus `json:"status"` + TrustLevel TrustLevel `json:"trust_level"` // Block or suspension details, if applicable. Null if the author is enabled. Block *Block `json:"block"` // Risk assessment details, if available. @@ -277,9 +277,9 @@ func (p *PublicAuthor) GetLastIncident() optionalnullable.OptionalNullable[float return p.LastIncident } -func (p *PublicAuthor) GetStatus() Status { +func (p *PublicAuthor) GetStatus() PublicAuthorStatus { if p == nil { - return Status("") + return PublicAuthorStatus("") } return p.Status } diff --git a/internal/sdk/models/components/queue.go b/internal/sdk/models/components/queue.go new file mode 100644 index 0000000..67f0ddf --- /dev/null +++ b/internal/sdk/models/components/queue.go @@ -0,0 +1,42 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +type Queue struct { + // The queue ID. + ID string `json:"id"` + // The queue name. + Name string `json:"name"` + // The queue description. + Description *string `json:"description"` + // Whether this is the default queue of the project. + IsDefault bool `json:"isDefault"` +} + +func (q *Queue) GetID() string { + if q == nil { + return "" + } + return q.ID +} + +func (q *Queue) GetName() string { + if q == nil { + return "" + } + return q.Name +} + +func (q *Queue) GetDescription() *string { + if q == nil { + return nil + } + return q.Description +} + +func (q *Queue) GetIsDefault() bool { + if q == nil { + return false + } + return q.IsDefault +} diff --git a/internal/sdk/models/components/queueitem.go b/internal/sdk/models/components/queueitem.go new file mode 100644 index 0000000..0b5f5c3 --- /dev/null +++ b/internal/sdk/models/components/queueitem.go @@ -0,0 +1,1078 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/moderation-api/cli/internal/sdk/optionalnullable" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// QueueItemContentLeafAudio - Audio +type QueueItemContentLeafAudio struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"audio" json:"type"` + // The URL of the audio content + URL string `json:"url"` +} + +func (q QueueItemContentLeafAudio) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemContentLeafAudio) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemContentLeafAudio) GetType() string { + return "audio" +} + +func (q *QueueItemContentLeafAudio) GetURL() string { + if q == nil { + return "" + } + return q.URL +} + +// QueueItemContentLeafVideo - Video +type QueueItemContentLeafVideo struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"video" json:"type"` + // A public URL of the video content + URL string `json:"url"` +} + +func (q QueueItemContentLeafVideo) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemContentLeafVideo) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemContentLeafVideo) GetType() string { + return "video" +} + +func (q *QueueItemContentLeafVideo) GetURL() string { + if q == nil { + return "" + } + return q.URL +} + +// QueueItemContentLeafImage - Image +type QueueItemContentLeafImage struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"image" json:"type"` + // A public URL of the image content. Either url or data must be provided. + URL *string `json:"url,omitzero"` + // 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. + Data *string `json:"data,omitzero"` +} + +func (q QueueItemContentLeafImage) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemContentLeafImage) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemContentLeafImage) GetType() string { + return "image" +} + +func (q *QueueItemContentLeafImage) GetURL() *string { + if q == nil { + return nil + } + return q.URL +} + +func (q *QueueItemContentLeafImage) GetData() *string { + if q == nil { + return nil + } + return q.Data +} + +// QueueItemContentLeafText - Text +type QueueItemContentLeafText struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"text" json:"type"` + // The content text + Text string `json:"text"` +} + +func (q QueueItemContentLeafText) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemContentLeafText) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemContentLeafText) GetType() string { + return "text" +} + +func (q *QueueItemContentLeafText) GetText() string { + if q == nil { + return "" + } + return q.Text +} + +type QueueItemContentLeafType string + +const ( + QueueItemContentLeafTypeText QueueItemContentLeafType = "text" + QueueItemContentLeafTypeImage QueueItemContentLeafType = "image" + QueueItemContentLeafTypeVideo QueueItemContentLeafType = "video" + QueueItemContentLeafTypeAudio QueueItemContentLeafType = "audio" + QueueItemContentLeafTypeUnknown QueueItemContentLeafType = "UNKNOWN" +) + +type QueueItemContentLeaf struct { + QueueItemContentLeafText *QueueItemContentLeafText `queryParam:"inline" union:"member"` + QueueItemContentLeafImage *QueueItemContentLeafImage `queryParam:"inline" union:"member"` + QueueItemContentLeafVideo *QueueItemContentLeafVideo `queryParam:"inline" union:"member"` + QueueItemContentLeafAudio *QueueItemContentLeafAudio `queryParam:"inline" union:"member"` + UnknownRaw json.RawMessage `json:"-" union:"unknown"` + + Type QueueItemContentLeafType +} + +func CreateQueueItemContentLeafText(text QueueItemContentLeafText) QueueItemContentLeaf { + typ := QueueItemContentLeafTypeText + + return QueueItemContentLeaf{ + QueueItemContentLeafText: &text, + Type: typ, + } +} + +func CreateQueueItemContentLeafImage(image QueueItemContentLeafImage) QueueItemContentLeaf { + typ := QueueItemContentLeafTypeImage + + return QueueItemContentLeaf{ + QueueItemContentLeafImage: &image, + Type: typ, + } +} + +func CreateQueueItemContentLeafVideo(video QueueItemContentLeafVideo) QueueItemContentLeaf { + typ := QueueItemContentLeafTypeVideo + + return QueueItemContentLeaf{ + QueueItemContentLeafVideo: &video, + Type: typ, + } +} + +func CreateQueueItemContentLeafAudio(audio QueueItemContentLeafAudio) QueueItemContentLeaf { + typ := QueueItemContentLeafTypeAudio + + return QueueItemContentLeaf{ + QueueItemContentLeafAudio: &audio, + Type: typ, + } +} + +func CreateQueueItemContentLeafUnknown(raw json.RawMessage) QueueItemContentLeaf { + return QueueItemContentLeaf{ + UnknownRaw: raw, + Type: QueueItemContentLeafTypeUnknown, + } +} + +func (u QueueItemContentLeaf) GetUnknownRaw() json.RawMessage { + return u.UnknownRaw +} + +func (u QueueItemContentLeaf) IsUnknown() bool { + return u.Type == QueueItemContentLeafTypeUnknown +} + +func (u *QueueItemContentLeaf) UnmarshalJSON(data []byte) (err error) { + previous := *u + *u = QueueItemContentLeaf{} + defer func() { + if err != nil { + *u = previous + } + }() + + type discriminator struct { + Type string `json:"type"` + } + + dis := new(discriminator) + if err := json.Unmarshal(data, &dis); err != nil { + var syntaxErr *json.SyntaxError + if errors.As(err, &syntaxErr) { + return fmt.Errorf("could not unmarshal discriminator: %w", err) + } + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentLeafTypeUnknown + return nil + } + if dis == nil { + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentLeafTypeUnknown + return nil + } + + switch dis.Type { + case "text": + queueItemContentLeafText := new(QueueItemContentLeafText) + if err := utils.UnmarshalJSON(data, &queueItemContentLeafText, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == text) type QueueItemContentLeafText within QueueItemContentLeaf: %w", string(data), err) + } + + u.QueueItemContentLeafText = queueItemContentLeafText + u.Type = QueueItemContentLeafTypeText + return nil + case "image": + queueItemContentLeafImage := new(QueueItemContentLeafImage) + if err := utils.UnmarshalJSON(data, &queueItemContentLeafImage, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == image) type QueueItemContentLeafImage within QueueItemContentLeaf: %w", string(data), err) + } + + u.QueueItemContentLeafImage = queueItemContentLeafImage + u.Type = QueueItemContentLeafTypeImage + return nil + case "video": + queueItemContentLeafVideo := new(QueueItemContentLeafVideo) + if err := utils.UnmarshalJSON(data, &queueItemContentLeafVideo, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == video) type QueueItemContentLeafVideo within QueueItemContentLeaf: %w", string(data), err) + } + + u.QueueItemContentLeafVideo = queueItemContentLeafVideo + u.Type = QueueItemContentLeafTypeVideo + return nil + case "audio": + queueItemContentLeafAudio := new(QueueItemContentLeafAudio) + if err := utils.UnmarshalJSON(data, &queueItemContentLeafAudio, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == audio) type QueueItemContentLeafAudio within QueueItemContentLeaf: %w", string(data), err) + } + + u.QueueItemContentLeafAudio = queueItemContentLeafAudio + u.Type = QueueItemContentLeafTypeAudio + return nil + default: + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentLeafTypeUnknown + return nil + } + +} + +func (u QueueItemContentLeaf) MarshalJSON() ([]byte, error) { + if u.QueueItemContentLeafText != nil { + return utils.MarshalJSON(u.QueueItemContentLeafText, "", true) + } + + if u.QueueItemContentLeafImage != nil { + return utils.MarshalJSON(u.QueueItemContentLeafImage, "", true) + } + + if u.QueueItemContentLeafVideo != nil { + return utils.MarshalJSON(u.QueueItemContentLeafVideo, "", true) + } + + if u.QueueItemContentLeafAudio != nil { + return utils.MarshalJSON(u.QueueItemContentLeafAudio, "", true) + } + + if u.UnknownRaw != nil { + return json.RawMessage(u.UnknownRaw), nil + } + return nil, errors.New("could not marshal union type QueueItemContentLeaf: all fields are null") +} + +// QueueItemObject - Object +type QueueItemObject struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"object" json:"type"` + // Values in the object. Can be mixed content types. + Data map[string]QueueItemContentLeaf `json:"data"` +} + +func (q QueueItemObject) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemObject) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemObject) GetType() string { + return "object" +} + +func (q *QueueItemObject) GetData() map[string]QueueItemContentLeaf { + if q == nil { + return map[string]QueueItemContentLeaf{} + } + return q.Data +} + +// QueueItemAudio - Audio +type QueueItemAudio struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"audio" json:"type"` + // The URL of the audio content + URL string `json:"url"` +} + +func (q QueueItemAudio) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemAudio) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemAudio) GetType() string { + return "audio" +} + +func (q *QueueItemAudio) GetURL() string { + if q == nil { + return "" + } + return q.URL +} + +// QueueItemVideo - Video +type QueueItemVideo struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"video" json:"type"` + // A public URL of the video content + URL string `json:"url"` +} + +func (q QueueItemVideo) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemVideo) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemVideo) GetType() string { + return "video" +} + +func (q *QueueItemVideo) GetURL() string { + if q == nil { + return "" + } + return q.URL +} + +// QueueItemImage - Image +type QueueItemImage struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"image" json:"type"` + // A public URL of the image content. Either url or data must be provided. + URL *string `json:"url,omitzero"` + // 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. + Data *string `json:"data,omitzero"` +} + +func (q QueueItemImage) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemImage) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemImage) GetType() string { + return "image" +} + +func (q *QueueItemImage) GetURL() *string { + if q == nil { + return nil + } + return q.URL +} + +func (q *QueueItemImage) GetData() *string { + if q == nil { + return nil + } + return q.Data +} + +// QueueItemText - Text +type QueueItemText struct { + //lint:ignore U1000 accessed via reflection for JSON marshaling + type_ string `const:"text" json:"type"` + // The content text + Text string `json:"text"` +} + +func (q QueueItemText) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemText) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemText) GetType() string { + return "text" +} + +func (q *QueueItemText) GetText() string { + if q == nil { + return "" + } + return q.Text +} + +type QueueItemContentType string + +const ( + QueueItemContentTypeText QueueItemContentType = "text" + QueueItemContentTypeImage QueueItemContentType = "image" + QueueItemContentTypeVideo QueueItemContentType = "video" + QueueItemContentTypeAudio QueueItemContentType = "audio" + QueueItemContentTypeObject QueueItemContentType = "object" + QueueItemContentTypeUnknown QueueItemContentType = "UNKNOWN" +) + +type QueueItemContent struct { + QueueItemText *QueueItemText `queryParam:"inline" union:"member"` + QueueItemImage *QueueItemImage `queryParam:"inline" union:"member"` + QueueItemVideo *QueueItemVideo `queryParam:"inline" union:"member"` + QueueItemAudio *QueueItemAudio `queryParam:"inline" union:"member"` + QueueItemObject *QueueItemObject `queryParam:"inline" union:"member"` + UnknownRaw json.RawMessage `json:"-" union:"unknown"` + + Type QueueItemContentType +} + +func CreateQueueItemContentText(text QueueItemText) QueueItemContent { + typ := QueueItemContentTypeText + + return QueueItemContent{ + QueueItemText: &text, + Type: typ, + } +} + +func CreateQueueItemContentImage(image QueueItemImage) QueueItemContent { + typ := QueueItemContentTypeImage + + return QueueItemContent{ + QueueItemImage: &image, + Type: typ, + } +} + +func CreateQueueItemContentVideo(video QueueItemVideo) QueueItemContent { + typ := QueueItemContentTypeVideo + + return QueueItemContent{ + QueueItemVideo: &video, + Type: typ, + } +} + +func CreateQueueItemContentAudio(audio QueueItemAudio) QueueItemContent { + typ := QueueItemContentTypeAudio + + return QueueItemContent{ + QueueItemAudio: &audio, + Type: typ, + } +} + +func CreateQueueItemContentObject(object QueueItemObject) QueueItemContent { + typ := QueueItemContentTypeObject + + return QueueItemContent{ + QueueItemObject: &object, + Type: typ, + } +} + +func CreateQueueItemContentUnknown(raw json.RawMessage) QueueItemContent { + return QueueItemContent{ + UnknownRaw: raw, + Type: QueueItemContentTypeUnknown, + } +} + +func (u QueueItemContent) GetUnknownRaw() json.RawMessage { + return u.UnknownRaw +} + +func (u QueueItemContent) IsUnknown() bool { + return u.Type == QueueItemContentTypeUnknown +} + +func (u *QueueItemContent) UnmarshalJSON(data []byte) (err error) { + previous := *u + *u = QueueItemContent{} + defer func() { + if err != nil { + *u = previous + } + }() + + type discriminator struct { + Type string `json:"type"` + } + + dis := new(discriminator) + if err := json.Unmarshal(data, &dis); err != nil { + var syntaxErr *json.SyntaxError + if errors.As(err, &syntaxErr) { + return fmt.Errorf("could not unmarshal discriminator: %w", err) + } + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentTypeUnknown + return nil + } + if dis == nil { + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentTypeUnknown + return nil + } + + switch dis.Type { + case "text": + queueItemText := new(QueueItemText) + if err := utils.UnmarshalJSON(data, &queueItemText, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == text) type QueueItemText within QueueItemContent: %w", string(data), err) + } + + u.QueueItemText = queueItemText + u.Type = QueueItemContentTypeText + return nil + case "image": + queueItemImage := new(QueueItemImage) + if err := utils.UnmarshalJSON(data, &queueItemImage, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == image) type QueueItemImage within QueueItemContent: %w", string(data), err) + } + + u.QueueItemImage = queueItemImage + u.Type = QueueItemContentTypeImage + return nil + case "video": + queueItemVideo := new(QueueItemVideo) + if err := utils.UnmarshalJSON(data, &queueItemVideo, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == video) type QueueItemVideo within QueueItemContent: %w", string(data), err) + } + + u.QueueItemVideo = queueItemVideo + u.Type = QueueItemContentTypeVideo + return nil + case "audio": + queueItemAudio := new(QueueItemAudio) + if err := utils.UnmarshalJSON(data, &queueItemAudio, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == audio) type QueueItemAudio within QueueItemContent: %w", string(data), err) + } + + u.QueueItemAudio = queueItemAudio + u.Type = QueueItemContentTypeAudio + return nil + case "object": + queueItemObject := new(QueueItemObject) + if err := utils.UnmarshalJSON(data, &queueItemObject, "", true, nil); err != nil { + return fmt.Errorf("could not unmarshal `%s` into expected (Type == object) type QueueItemObject within QueueItemContent: %w", string(data), err) + } + + u.QueueItemObject = queueItemObject + u.Type = QueueItemContentTypeObject + return nil + default: + u.UnknownRaw = json.RawMessage(data) + u.Type = QueueItemContentTypeUnknown + return nil + } + +} + +func (u QueueItemContent) MarshalJSON() ([]byte, error) { + if u.QueueItemText != nil { + return utils.MarshalJSON(u.QueueItemText, "", true) + } + + if u.QueueItemImage != nil { + return utils.MarshalJSON(u.QueueItemImage, "", true) + } + + if u.QueueItemVideo != nil { + return utils.MarshalJSON(u.QueueItemVideo, "", true) + } + + if u.QueueItemAudio != nil { + return utils.MarshalJSON(u.QueueItemAudio, "", true) + } + + if u.QueueItemObject != nil { + return utils.MarshalJSON(u.QueueItemObject, "", true) + } + + if u.UnknownRaw != nil { + return json.RawMessage(u.UnknownRaw), nil + } + return nil, errors.New("could not marshal union type QueueItemContent: all fields are null") +} + +type QueueItemBrandImpersonation struct { + Brand string `json:"brand"` + Method string `json:"method"` +} + +func (q *QueueItemBrandImpersonation) GetBrand() string { + if q == nil { + return "" + } + return q.Brand +} + +func (q *QueueItemBrandImpersonation) GetMethod() string { + if q == nil { + return "" + } + return q.Method +} + +type QueueItemSignals struct { + BrandImpersonation *QueueItemBrandImpersonation `json:"brand_impersonation"` + HasSuspiciousCharacters bool `json:"has_suspicious_characters"` + IsLinkShortener bool `json:"is_link_shortener"` + DomainAgeDays *float64 `json:"domain_age_days"` + HasEmailSetup *bool `json:"has_email_setup"` + RedirectCount *float64 `json:"redirect_count"` + FinalURL *string `json:"final_url"` + BotProtection *bool `json:"bot_protection"` + IsReported bool `json:"is_reported"` +} + +func (q *QueueItemSignals) GetBrandImpersonation() *QueueItemBrandImpersonation { + if q == nil { + return nil + } + return q.BrandImpersonation +} + +func (q *QueueItemSignals) GetHasSuspiciousCharacters() bool { + if q == nil { + return false + } + return q.HasSuspiciousCharacters +} + +func (q *QueueItemSignals) GetIsLinkShortener() bool { + if q == nil { + return false + } + return q.IsLinkShortener +} + +func (q *QueueItemSignals) GetDomainAgeDays() *float64 { + if q == nil { + return nil + } + return q.DomainAgeDays +} + +func (q *QueueItemSignals) GetHasEmailSetup() *bool { + if q == nil { + return nil + } + return q.HasEmailSetup +} + +func (q *QueueItemSignals) GetRedirectCount() *float64 { + if q == nil { + return nil + } + return q.RedirectCount +} + +func (q *QueueItemSignals) GetFinalURL() *string { + if q == nil { + return nil + } + return q.FinalURL +} + +func (q *QueueItemSignals) GetBotProtection() *bool { + if q == nil { + return nil + } + return q.BotProtection +} + +func (q *QueueItemSignals) GetIsReported() bool { + if q == nil { + return false + } + return q.IsReported +} + +type QueueItemMatch struct { + // The matched substring + Match string `json:"match"` + // Match confidence between 0 and 1 + Probability float64 `json:"probability"` + // [start, end] character offsets in the source text + Span []float64 `json:"span"` + Mask optionalnullable.OptionalNullable[string] `json:"mask,omitzero"` + Reasons []string `json:"reasons,omitzero"` + Signals *QueueItemSignals `json:"signals,omitzero"` + EntityType *string `json:"entity_type,omitzero"` + Scripts []string `json:"scripts,omitzero"` + Skeleton *string `json:"skeleton,omitzero"` +} + +func (q QueueItemMatch) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(q, "", false) +} + +func (q *QueueItemMatch) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &q, "", false, nil); err != nil { + return err + } + return nil +} + +func (q *QueueItemMatch) GetMatch() string { + if q == nil { + return "" + } + return q.Match +} + +func (q *QueueItemMatch) GetProbability() float64 { + if q == nil { + return 0.0 + } + return q.Probability +} + +func (q *QueueItemMatch) GetSpan() []float64 { + if q == nil { + return []float64{} + } + return q.Span +} + +func (q *QueueItemMatch) GetMask() optionalnullable.OptionalNullable[string] { + if q == nil { + return nil + } + return q.Mask +} + +func (q *QueueItemMatch) GetReasons() []string { + if q == nil { + return nil + } + return q.Reasons +} + +func (q *QueueItemMatch) GetSignals() *QueueItemSignals { + if q == nil { + return nil + } + return q.Signals +} + +func (q *QueueItemMatch) GetEntityType() *string { + if q == nil { + return nil + } + return q.EntityType +} + +func (q *QueueItemMatch) GetScripts() []string { + if q == nil { + return nil + } + return q.Scripts +} + +func (q *QueueItemMatch) GetSkeleton() *string { + if q == nil { + return nil + } + return q.Skeleton +} + +type Label struct { + // The label name + Label string `json:"label"` + // Confidence score between 0 and 1 + Score float64 `json:"score"` + // True if the label was applied manually by a moderator + Manual *bool `json:"manual,omitzero"` + // Whether this label crossed its flagging threshold + Flagged *bool `json:"flagged,omitzero"` + // Whether this label crossed its threshold under a shadowed policy — reported for visibility, never counted toward a flagging decision + ShadowFlagged *bool `json:"shadow_flagged,omitzero"` + Matches []QueueItemMatch `json:"matches,omitzero"` +} + +func (l Label) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(l, "", false) +} + +func (l *Label) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &l, "", false, nil); err != nil { + return err + } + return nil +} + +func (l *Label) GetLabel() string { + if l == nil { + return "" + } + return l.Label +} + +func (l *Label) GetScore() float64 { + if l == nil { + return 0.0 + } + return l.Score +} + +func (l *Label) GetManual() *bool { + if l == nil { + return nil + } + return l.Manual +} + +func (l *Label) GetFlagged() *bool { + if l == nil { + return nil + } + return l.Flagged +} + +func (l *Label) GetShadowFlagged() *bool { + if l == nil { + return nil + } + return l.ShadowFlagged +} + +func (l *Label) GetMatches() []QueueItemMatch { + if l == nil { + return nil + } + return l.Matches +} + +// QueueItemStatus - `pending` waits for review. `resolved` is reviewed. +type QueueItemStatus string + +const ( + QueueItemStatusPending QueueItemStatus = "pending" + QueueItemStatusResolved QueueItemStatus = "resolved" +) + +func (e QueueItemStatus) ToPointer() *QueueItemStatus { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *QueueItemStatus) IsExact() bool { + if e != nil { + switch *e { + case "pending", "resolved": + return true + } + } + return false +} + +type QueueItem struct { + // The item ID. Use it to resolve or unresolve the item in this queue. + ID string `json:"id"` + // Your content ID, as you sent it to `POST /moderate`. + ContentID string `json:"contentId"` + // The submitted content. + Content *QueueItemContent `json:"content"` + // The moderation labels of the content. + Labels []Label `json:"labels"` + // Whether moderation flagged the content. + Flagged bool `json:"flagged"` + // Severity score from 0 to 1, if available. + Severity *float64 `json:"severity"` + // `pending` waits for review. `resolved` is reviewed. + Status QueueItemStatus `json:"status"` + // When the content was submitted, as Unix time in milliseconds. + Timestamp float64 `json:"timestamp"` + // Detected ISO language code. + Language *string `json:"language"` + // Conversation ID, if any. + ConversationID *string `json:"conversationId"` + // Your author ID, if any. + AuthorID *string `json:"authorId"` + // Content type, for example `message` or `profile`. + ContentType *string `json:"contentType"` +} + +func (q *QueueItem) GetID() string { + if q == nil { + return "" + } + return q.ID +} + +func (q *QueueItem) GetContentID() string { + if q == nil { + return "" + } + return q.ContentID +} + +func (q *QueueItem) GetContent() *QueueItemContent { + if q == nil { + return nil + } + return q.Content +} + +func (q *QueueItem) GetContentText() *QueueItemText { + if v := q.GetContent(); v != nil { + return v.QueueItemText + } + return nil +} + +func (q *QueueItem) GetContentImage() *QueueItemImage { + if v := q.GetContent(); v != nil { + return v.QueueItemImage + } + return nil +} + +func (q *QueueItem) GetContentVideo() *QueueItemVideo { + if v := q.GetContent(); v != nil { + return v.QueueItemVideo + } + return nil +} + +func (q *QueueItem) GetContentAudio() *QueueItemAudio { + if v := q.GetContent(); v != nil { + return v.QueueItemAudio + } + return nil +} + +func (q *QueueItem) GetContentObject() *QueueItemObject { + if v := q.GetContent(); v != nil { + return v.QueueItemObject + } + return nil +} + +func (q *QueueItem) GetLabels() []Label { + if q == nil { + return []Label{} + } + return q.Labels +} + +func (q *QueueItem) GetFlagged() bool { + if q == nil { + return false + } + return q.Flagged +} + +func (q *QueueItem) GetSeverity() *float64 { + if q == nil { + return nil + } + return q.Severity +} + +func (q *QueueItem) GetStatus() QueueItemStatus { + if q == nil { + return QueueItemStatus("") + } + return q.Status +} + +func (q *QueueItem) GetTimestamp() float64 { + if q == nil { + return 0.0 + } + return q.Timestamp +} + +func (q *QueueItem) GetLanguage() *string { + if q == nil { + return nil + } + return q.Language +} + +func (q *QueueItem) GetConversationID() *string { + if q == nil { + return nil + } + return q.ConversationID +} + +func (q *QueueItem) GetAuthorID() *string { + if q == nil { + return nil + } + return q.AuthorID +} + +func (q *QueueItem) GetContentType() *string { + if q == nil { + return nil + } + return q.ContentType +} diff --git a/internal/sdk/models/components/queuewithcounts.go b/internal/sdk/models/components/queuewithcounts.go new file mode 100644 index 0000000..93045dc --- /dev/null +++ b/internal/sdk/models/components/queuewithcounts.go @@ -0,0 +1,69 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +type QueueWithCounts struct { + // The queue ID. + ID string `json:"id"` + // The queue name. + Name string `json:"name"` + // The queue description. + Description *string `json:"description"` + // Whether this is the default queue of the project. + IsDefault bool `json:"isDefault"` + // Number of items that wait for review. + UnresolvedItemsCount int64 `json:"unresolvedItemsCount"` + // Number of items that are resolved. + ResolvedItemsCount int64 `json:"resolvedItemsCount"` + // Number of items in the queue, resolved and unresolved. + TotalItemsCount int64 `json:"totalItemsCount"` +} + +func (q *QueueWithCounts) GetID() string { + if q == nil { + return "" + } + return q.ID +} + +func (q *QueueWithCounts) GetName() string { + if q == nil { + return "" + } + return q.Name +} + +func (q *QueueWithCounts) GetDescription() *string { + if q == nil { + return nil + } + return q.Description +} + +func (q *QueueWithCounts) GetIsDefault() bool { + if q == nil { + return false + } + return q.IsDefault +} + +func (q *QueueWithCounts) GetUnresolvedItemsCount() int64 { + if q == nil { + return 0 + } + return q.UnresolvedItemsCount +} + +func (q *QueueWithCounts) GetResolvedItemsCount() int64 { + if q == nil { + return 0 + } + return q.ResolvedItemsCount +} + +func (q *QueueWithCounts) GetTotalItemsCount() int64 { + if q == nil { + return 0 + } + return q.TotalItemsCount +} diff --git a/internal/sdk/models/operations/createfeedback.go b/internal/sdk/models/operations/createfeedback.go new file mode 100644 index 0000000..37eb116 --- /dev/null +++ b/internal/sdk/models/operations/createfeedback.go @@ -0,0 +1,205 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// CreateFeedbackType - The kind of feedback. +type CreateFeedbackType string + +const ( + CreateFeedbackTypeFeatureRequest CreateFeedbackType = "feature_request" + CreateFeedbackTypeBug CreateFeedbackType = "bug" + CreateFeedbackTypeOther CreateFeedbackType = "other" +) + +func (e CreateFeedbackType) ToPointer() *CreateFeedbackType { + return &e +} +func (e *CreateFeedbackType) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "feature_request": + fallthrough + case "bug": + fallthrough + case "other": + *e = CreateFeedbackType(v) + return nil + default: + return fmt.Errorf("invalid value for CreateFeedbackType: %v", v) + } +} + +// Source - Where the feedback was written: MCP server, agent skill, CLI or API. +type Source string + +const ( + SourceMcp Source = "mcp" + SourceSkill Source = "skill" + SourceCli Source = "cli" + SourceAPI Source = "api" +) + +func (e Source) ToPointer() *Source { + return &e +} +func (e *Source) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "mcp": + fallthrough + case "skill": + fallthrough + case "cli": + fallthrough + case "api": + *e = Source(v) + return nil + default: + return fmt.Errorf("invalid value for Source: %v", v) + } +} + +type CreateFeedbackRequestBody struct { + // A short summary of the request. + Title string `json:"title"` + // What you tried to do, why, and what is missing or does not work. Plain text. Blank lines start new paragraphs. + Description string `json:"description"` + // The kind of feedback. + Type *CreateFeedbackType `default:"feature_request" json:"type"` + // Where the feedback was written: MCP server, agent skill, CLI or API. + Source *Source `default:"api" json:"source"` +} + +func (c CreateFeedbackRequestBody) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateFeedbackRequestBody) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateFeedbackRequestBody) GetTitle() string { + if c == nil { + return "" + } + return c.Title +} + +func (c *CreateFeedbackRequestBody) GetDescription() string { + if c == nil { + return "" + } + return c.Description +} + +func (c *CreateFeedbackRequestBody) GetType() *CreateFeedbackType { + if c == nil { + return nil + } + return c.Type +} + +func (c *CreateFeedbackRequestBody) GetSource() *Source { + if c == nil { + return nil + } + return c.Source +} + +type CreateFeedbackRequest struct { + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body CreateFeedbackRequestBody `request:"mediaType=application/json"` +} + +func (c *CreateFeedbackRequest) GetProjectID() *string { + if c == nil { + return nil + } + return c.ProjectID +} + +func (c *CreateFeedbackRequest) GetBody() CreateFeedbackRequestBody { + if c == nil { + return CreateFeedbackRequestBody{} + } + return c.Body +} + +// CreateFeedbackResponseBody - Feedback received +type CreateFeedbackResponseBody struct { + // ID of the feedback. + ID string `json:"id"` + // The team received the feedback. + //lint:ignore U1000 accessed via reflection for JSON marshaling + status string `const:"received" json:"status"` +} + +func (c CreateFeedbackResponseBody) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateFeedbackResponseBody) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateFeedbackResponseBody) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CreateFeedbackResponseBody) GetStatus() string { + return "received" +} + +type CreateFeedbackResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Feedback received + Object *CreateFeedbackResponseBody +} + +func (c CreateFeedbackResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateFeedbackResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateFeedbackResponse) GetHTTPMeta() components.HTTPMetadata { + if c == nil { + return components.HTTPMetadata{} + } + return c.HTTPMeta +} + +func (c *CreateFeedbackResponse) GetObject() *CreateFeedbackResponseBody { + if c == nil { + return nil + } + return c.Object +} diff --git a/internal/sdk/models/operations/createwordlist.go b/internal/sdk/models/operations/createwordlist.go new file mode 100644 index 0000000..d9d8087 --- /dev/null +++ b/internal/sdk/models/operations/createwordlist.go @@ -0,0 +1,174 @@ +// 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 CreateWordlistRequestBody struct { + // Name of the wordlist + Name string `json:"name"` + // Key of the wordlist. Default: made from the name. If the key is already in use, a suffix is added. + Key *string `json:"key,omitzero"` + // Description of the wordlist + Description *string `json:"description,omitzero"` + // Words to add to the wordlist. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words. + Words []string `json:"words,omitzero"` +} + +func (c CreateWordlistRequestBody) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateWordlistRequestBody) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateWordlistRequestBody) GetName() string { + if c == nil { + return "" + } + return c.Name +} + +func (c *CreateWordlistRequestBody) GetKey() *string { + if c == nil { + return nil + } + return c.Key +} + +func (c *CreateWordlistRequestBody) GetDescription() *string { + if c == nil { + return nil + } + return c.Description +} + +func (c *CreateWordlistRequestBody) GetWords() []string { + if c == nil { + return nil + } + return c.Words +} + +type CreateWordlistRequest struct { + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body CreateWordlistRequestBody `request:"mediaType=application/json"` +} + +func (c *CreateWordlistRequest) GetProjectID() *string { + if c == nil { + return nil + } + return c.ProjectID +} + +func (c *CreateWordlistRequest) GetBody() CreateWordlistRequestBody { + if c == nil { + return CreateWordlistRequestBody{} + } + return c.Body +} + +// CreateWordlistResponseBody - Wordlist created successfully +type CreateWordlistResponseBody struct { + // ID of the wordlist + ID string `json:"id"` + Name *string `json:"name"` + // Words in the wordlist + Words []string `json:"words"` + // Creation date of the wordlist + CreatedAt string `json:"createdAt"` + // ID of the organization + OrganizationID string `json:"organizationId"` + UserID *string `json:"userId"` + // Strict mode + Strict bool `json:"strict"` +} + +func (c *CreateWordlistResponseBody) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *CreateWordlistResponseBody) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *CreateWordlistResponseBody) GetWords() []string { + if c == nil { + return []string{} + } + return c.Words +} + +func (c *CreateWordlistResponseBody) GetCreatedAt() string { + if c == nil { + return "" + } + return c.CreatedAt +} + +func (c *CreateWordlistResponseBody) GetOrganizationID() string { + if c == nil { + return "" + } + return c.OrganizationID +} + +func (c *CreateWordlistResponseBody) GetUserID() *string { + if c == nil { + return nil + } + return c.UserID +} + +func (c *CreateWordlistResponseBody) GetStrict() bool { + if c == nil { + return false + } + return c.Strict +} + +type CreateWordlistResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Wordlist created successfully + Object *CreateWordlistResponseBody +} + +func (c CreateWordlistResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *CreateWordlistResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *CreateWordlistResponse) GetHTTPMeta() components.HTTPMetadata { + if c == nil { + return components.HTTPMetadata{} + } + return c.HTTPMeta +} + +func (c *CreateWordlistResponse) GetObject() *CreateWordlistResponseBody { + if c == nil { + return nil + } + return c.Object +} diff --git a/internal/sdk/models/operations/getanalyticsauthors.go b/internal/sdk/models/operations/getanalyticsauthors.go new file mode 100644 index 0000000..c8e01e3 --- /dev/null +++ b/internal/sdk/models/operations/getanalyticsauthors.go @@ -0,0 +1,208 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsAuthorsQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsAuthorsQueryParamRange string + +const ( + GetAnalyticsAuthorsQueryParamRangeTwentyFourh GetAnalyticsAuthorsQueryParamRange = "24h" + GetAnalyticsAuthorsQueryParamRangeSevend GetAnalyticsAuthorsQueryParamRange = "7d" + GetAnalyticsAuthorsQueryParamRangeThirtyd GetAnalyticsAuthorsQueryParamRange = "30d" + GetAnalyticsAuthorsQueryParamRangeNinetyd GetAnalyticsAuthorsQueryParamRange = "90d" +) + +func (e GetAnalyticsAuthorsQueryParamRange) ToPointer() *GetAnalyticsAuthorsQueryParamRange { + return &e +} +func (e *GetAnalyticsAuthorsQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsAuthorsQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsAuthorsQueryParamRange: %v", v) + } +} + +type GetAnalyticsAuthorsRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsAuthorsQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + Channel *string `queryParam:"style=form,explode=true,name=channel"` + Limit *int64 `default:"6" queryParam:"style=form,explode=true,name=limit"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsAuthorsRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsAuthorsRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsAuthorsRequest) GetRange() *GetAnalyticsAuthorsQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsAuthorsRequest) GetChannel() *string { + if g == nil { + return nil + } + return g.Channel +} + +func (g *GetAnalyticsAuthorsRequest) GetLimit() *int64 { + if g == nil { + return nil + } + return g.Limit +} + +func (g *GetAnalyticsAuthorsRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsAuthorsRangeResponse - The time window of the numbers. +type GetAnalyticsAuthorsRangeResponse string + +const ( + GetAnalyticsAuthorsRangeResponseTwentyFourh GetAnalyticsAuthorsRangeResponse = "24h" + GetAnalyticsAuthorsRangeResponseSevend GetAnalyticsAuthorsRangeResponse = "7d" + GetAnalyticsAuthorsRangeResponseThirtyd GetAnalyticsAuthorsRangeResponse = "30d" + GetAnalyticsAuthorsRangeResponseNinetyd GetAnalyticsAuthorsRangeResponse = "90d" +) + +func (e GetAnalyticsAuthorsRangeResponse) ToPointer() *GetAnalyticsAuthorsRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsAuthorsRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +type GetAnalyticsAuthorsAuthor struct { + // The author ID from your system. Use it with the authors endpoints. + AuthorID string `json:"authorId"` + // The author name, if known. + Name *string `json:"name"` + // Flagged requests from this author in the window. + Flagged int64 `json:"flagged"` +} + +func (g *GetAnalyticsAuthorsAuthor) GetAuthorID() string { + if g == nil { + return "" + } + return g.AuthorID +} + +func (g *GetAnalyticsAuthorsAuthor) GetName() *string { + if g == nil { + return nil + } + return g.Name +} + +func (g *GetAnalyticsAuthorsAuthor) GetFlagged() int64 { + if g == nil { + return 0 + } + return g.Flagged +} + +// GetAnalyticsAuthorsResponseBody - Successful response +type GetAnalyticsAuthorsResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsAuthorsRangeResponse `json:"range"` + // The channel the numbers are for. Null when they are for all channels. + ChannelID *string `json:"channelId"` + // The authors with the most flagged requests, largest first. Requests without an author are not included. + Authors []GetAnalyticsAuthorsAuthor `json:"authors"` +} + +func (g *GetAnalyticsAuthorsResponseBody) GetRange() GetAnalyticsAuthorsRangeResponse { + if g == nil { + return GetAnalyticsAuthorsRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsAuthorsResponseBody) GetChannelID() *string { + if g == nil { + return nil + } + return g.ChannelID +} + +func (g *GetAnalyticsAuthorsResponseBody) GetAuthors() []GetAnalyticsAuthorsAuthor { + if g == nil { + return []GetAnalyticsAuthorsAuthor{} + } + return g.Authors +} + +type GetAnalyticsAuthorsResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsAuthorsResponseBody +} + +func (g GetAnalyticsAuthorsResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsAuthorsResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsAuthorsResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsAuthorsResponse) GetObject() *GetAnalyticsAuthorsResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getanalyticsautomation.go b/internal/sdk/models/operations/getanalyticsautomation.go new file mode 100644 index 0000000..0ba645a --- /dev/null +++ b/internal/sdk/models/operations/getanalyticsautomation.go @@ -0,0 +1,289 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsAutomationQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsAutomationQueryParamRange string + +const ( + GetAnalyticsAutomationQueryParamRangeTwentyFourh GetAnalyticsAutomationQueryParamRange = "24h" + GetAnalyticsAutomationQueryParamRangeSevend GetAnalyticsAutomationQueryParamRange = "7d" + GetAnalyticsAutomationQueryParamRangeThirtyd GetAnalyticsAutomationQueryParamRange = "30d" + GetAnalyticsAutomationQueryParamRangeNinetyd GetAnalyticsAutomationQueryParamRange = "90d" +) + +func (e GetAnalyticsAutomationQueryParamRange) ToPointer() *GetAnalyticsAutomationQueryParamRange { + return &e +} +func (e *GetAnalyticsAutomationQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsAutomationQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsAutomationQueryParamRange: %v", v) + } +} + +type GetAnalyticsAutomationRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsAutomationQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + Channel *string `queryParam:"style=form,explode=true,name=channel"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsAutomationRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsAutomationRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsAutomationRequest) GetRange() *GetAnalyticsAutomationQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsAutomationRequest) GetChannel() *string { + if g == nil { + return nil + } + return g.Channel +} + +func (g *GetAnalyticsAutomationRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsAutomationRangeResponse - The time window of the numbers. +type GetAnalyticsAutomationRangeResponse string + +const ( + GetAnalyticsAutomationRangeResponseTwentyFourh GetAnalyticsAutomationRangeResponse = "24h" + GetAnalyticsAutomationRangeResponseSevend GetAnalyticsAutomationRangeResponse = "7d" + GetAnalyticsAutomationRangeResponseThirtyd GetAnalyticsAutomationRangeResponse = "30d" + GetAnalyticsAutomationRangeResponseNinetyd GetAnalyticsAutomationRangeResponse = "90d" +) + +func (e GetAnalyticsAutomationRangeResponse) ToPointer() *GetAnalyticsAutomationRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsAutomationRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +type Rule struct { + // The rule name. "Other rules" holds all rules after the largest ones. + Name string `json:"name"` + // Flagged requests that this rule settled. + Count int64 `json:"count"` +} + +func (r *Rule) GetName() string { + if r == nil { + return "" + } + return r.Name +} + +func (r *Rule) GetCount() int64 { + if r == nil { + return 0 + } + return r.Count +} + +// GetAnalyticsAutomationResponseBody - Successful response +type GetAnalyticsAutomationResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsAutomationRangeResponse `json:"range"` + // The channel the numbers are for. Null when they are for all channels. + ChannelID *string `json:"channelId"` + // Flagged requests in the window. Counted on a rolling window from the raw requests, so it can be a little different from the summary. + Flagged int64 `json:"flagged"` + // Flagged requests that a rule allowed or rejected without human review. + HandledByRules int64 `json:"handledByRules"` + // Flagged requests that the casebook settled without human review. + HandledByCasebook int64 `json:"handledByCasebook"` + // Distinct content items sent to the review queue. Repeats of the same content count once, as in the queue. + SentToReview int64 `json:"sentToReview"` + // Flagged requests that the review queue merged into an item it already shows: repeats, edits and content that was settled later. + MergedDuplicates int64 `json:"mergedDuplicates"` + AutomationRate *float64 `json:"automationRate"` + // Review queue items from this window that a moderator allowed. + ReviewsAllowed int64 `json:"reviewsAllowed"` + // Review queue items from this window that a moderator rejected. + ReviewsRejected int64 `json:"reviewsRejected"` + // Review queue items from this window that a moderator marked as done without allow or reject. + ReviewsResolvedWithoutDecision int64 `json:"reviewsResolvedWithoutDecision"` + // Review queue items from this window that are still open. + ReviewsUnresolved int64 `json:"reviewsUnresolved"` + // Requests where the casebook decision was different from the decision based on severity. + CasebookOverrulings int64 `json:"casebookOverrulings"` + // The rules that settled flagged requests without review, largest first. The 5 largest have their own entry. The casebook is one of the rules. + Rules []Rule `json:"rules"` +} + +func (g *GetAnalyticsAutomationResponseBody) GetRange() GetAnalyticsAutomationRangeResponse { + if g == nil { + return GetAnalyticsAutomationRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsAutomationResponseBody) GetChannelID() *string { + if g == nil { + return nil + } + return g.ChannelID +} + +func (g *GetAnalyticsAutomationResponseBody) GetFlagged() int64 { + if g == nil { + return 0 + } + return g.Flagged +} + +func (g *GetAnalyticsAutomationResponseBody) GetHandledByRules() int64 { + if g == nil { + return 0 + } + return g.HandledByRules +} + +func (g *GetAnalyticsAutomationResponseBody) GetHandledByCasebook() int64 { + if g == nil { + return 0 + } + return g.HandledByCasebook +} + +func (g *GetAnalyticsAutomationResponseBody) GetSentToReview() int64 { + if g == nil { + return 0 + } + return g.SentToReview +} + +func (g *GetAnalyticsAutomationResponseBody) GetMergedDuplicates() int64 { + if g == nil { + return 0 + } + return g.MergedDuplicates +} + +func (g *GetAnalyticsAutomationResponseBody) GetAutomationRate() *float64 { + if g == nil { + return nil + } + return g.AutomationRate +} + +func (g *GetAnalyticsAutomationResponseBody) GetReviewsAllowed() int64 { + if g == nil { + return 0 + } + return g.ReviewsAllowed +} + +func (g *GetAnalyticsAutomationResponseBody) GetReviewsRejected() int64 { + if g == nil { + return 0 + } + return g.ReviewsRejected +} + +func (g *GetAnalyticsAutomationResponseBody) GetReviewsResolvedWithoutDecision() int64 { + if g == nil { + return 0 + } + return g.ReviewsResolvedWithoutDecision +} + +func (g *GetAnalyticsAutomationResponseBody) GetReviewsUnresolved() int64 { + if g == nil { + return 0 + } + return g.ReviewsUnresolved +} + +func (g *GetAnalyticsAutomationResponseBody) GetCasebookOverrulings() int64 { + if g == nil { + return 0 + } + return g.CasebookOverrulings +} + +func (g *GetAnalyticsAutomationResponseBody) GetRules() []Rule { + if g == nil { + return []Rule{} + } + return g.Rules +} + +type GetAnalyticsAutomationResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsAutomationResponseBody +} + +func (g GetAnalyticsAutomationResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsAutomationResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsAutomationResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsAutomationResponse) GetObject() *GetAnalyticsAutomationResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getanalyticschannels.go b/internal/sdk/models/operations/getanalyticschannels.go new file mode 100644 index 0000000..770295b --- /dev/null +++ b/internal/sdk/models/operations/getanalyticschannels.go @@ -0,0 +1,201 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsChannelsQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsChannelsQueryParamRange string + +const ( + GetAnalyticsChannelsQueryParamRangeTwentyFourh GetAnalyticsChannelsQueryParamRange = "24h" + GetAnalyticsChannelsQueryParamRangeSevend GetAnalyticsChannelsQueryParamRange = "7d" + GetAnalyticsChannelsQueryParamRangeThirtyd GetAnalyticsChannelsQueryParamRange = "30d" + GetAnalyticsChannelsQueryParamRangeNinetyd GetAnalyticsChannelsQueryParamRange = "90d" +) + +func (e GetAnalyticsChannelsQueryParamRange) ToPointer() *GetAnalyticsChannelsQueryParamRange { + return &e +} +func (e *GetAnalyticsChannelsQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsChannelsQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsChannelsQueryParamRange: %v", v) + } +} + +type GetAnalyticsChannelsRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsChannelsQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsChannelsRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsChannelsRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsChannelsRequest) GetRange() *GetAnalyticsChannelsQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsChannelsRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsChannelsRangeResponse - The time window of the numbers. +type GetAnalyticsChannelsRangeResponse string + +const ( + GetAnalyticsChannelsRangeResponseTwentyFourh GetAnalyticsChannelsRangeResponse = "24h" + GetAnalyticsChannelsRangeResponseSevend GetAnalyticsChannelsRangeResponse = "7d" + GetAnalyticsChannelsRangeResponseThirtyd GetAnalyticsChannelsRangeResponse = "30d" + GetAnalyticsChannelsRangeResponseNinetyd GetAnalyticsChannelsRangeResponse = "90d" +) + +func (e GetAnalyticsChannelsRangeResponse) ToPointer() *GetAnalyticsChannelsRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsChannelsRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +type Channel struct { + // The channel ID. + ID string `json:"id"` + // The channel name. + Name string `json:"name"` + // Requests received in this channel. + Requests int64 `json:"requests"` + // Requests flagged in this channel. + Flagged int64 `json:"flagged"` + // The share of requests in this channel that were flagged: flagged / requests. + FlagRate float64 `json:"flagRate"` +} + +func (c *Channel) GetID() string { + if c == nil { + return "" + } + return c.ID +} + +func (c *Channel) GetName() string { + if c == nil { + return "" + } + return c.Name +} + +func (c *Channel) GetRequests() int64 { + if c == nil { + return 0 + } + return c.Requests +} + +func (c *Channel) GetFlagged() int64 { + if c == nil { + return 0 + } + return c.Flagged +} + +func (c *Channel) GetFlagRate() float64 { + if c == nil { + return 0.0 + } + return c.FlagRate +} + +// GetAnalyticsChannelsResponseBody - Successful response +type GetAnalyticsChannelsResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsChannelsRangeResponse `json:"range"` + // Requests and flags per channel. Channels with no requests in the window are not included. + Channels []Channel `json:"channels"` +} + +func (g *GetAnalyticsChannelsResponseBody) GetRange() GetAnalyticsChannelsRangeResponse { + if g == nil { + return GetAnalyticsChannelsRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsChannelsResponseBody) GetChannels() []Channel { + if g == nil { + return []Channel{} + } + return g.Channels +} + +type GetAnalyticsChannelsResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsChannelsResponseBody +} + +func (g GetAnalyticsChannelsResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsChannelsResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsChannelsResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsChannelsResponse) GetObject() *GetAnalyticsChannelsResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getanalyticspolicies.go b/internal/sdk/models/operations/getanalyticspolicies.go new file mode 100644 index 0000000..9086bc4 --- /dev/null +++ b/internal/sdk/models/operations/getanalyticspolicies.go @@ -0,0 +1,208 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsPoliciesQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsPoliciesQueryParamRange string + +const ( + GetAnalyticsPoliciesQueryParamRangeTwentyFourh GetAnalyticsPoliciesQueryParamRange = "24h" + GetAnalyticsPoliciesQueryParamRangeSevend GetAnalyticsPoliciesQueryParamRange = "7d" + GetAnalyticsPoliciesQueryParamRangeThirtyd GetAnalyticsPoliciesQueryParamRange = "30d" + GetAnalyticsPoliciesQueryParamRangeNinetyd GetAnalyticsPoliciesQueryParamRange = "90d" +) + +func (e GetAnalyticsPoliciesQueryParamRange) ToPointer() *GetAnalyticsPoliciesQueryParamRange { + return &e +} +func (e *GetAnalyticsPoliciesQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsPoliciesQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsPoliciesQueryParamRange: %v", v) + } +} + +type GetAnalyticsPoliciesRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsPoliciesQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + Channel *string `queryParam:"style=form,explode=true,name=channel"` + Limit *int64 `default:"8" queryParam:"style=form,explode=true,name=limit"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsPoliciesRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsPoliciesRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsPoliciesRequest) GetRange() *GetAnalyticsPoliciesQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsPoliciesRequest) GetChannel() *string { + if g == nil { + return nil + } + return g.Channel +} + +func (g *GetAnalyticsPoliciesRequest) GetLimit() *int64 { + if g == nil { + return nil + } + return g.Limit +} + +func (g *GetAnalyticsPoliciesRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsPoliciesRangeResponse - The time window of the numbers. +type GetAnalyticsPoliciesRangeResponse string + +const ( + GetAnalyticsPoliciesRangeResponseTwentyFourh GetAnalyticsPoliciesRangeResponse = "24h" + GetAnalyticsPoliciesRangeResponseSevend GetAnalyticsPoliciesRangeResponse = "7d" + GetAnalyticsPoliciesRangeResponseThirtyd GetAnalyticsPoliciesRangeResponse = "30d" + GetAnalyticsPoliciesRangeResponseNinetyd GetAnalyticsPoliciesRangeResponse = "90d" +) + +func (e GetAnalyticsPoliciesRangeResponse) ToPointer() *GetAnalyticsPoliciesRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsPoliciesRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +type GetAnalyticsPoliciesPolicy struct { + // The policy label, for example `toxicity`. + Label string `json:"label"` + // Flagged requests with this label in the window. Repeats count each time. + Flagged int64 `json:"flagged"` + // Flagged requests with this label in the window of equal length before. + PreviousFlagged int64 `json:"previousFlagged"` +} + +func (g *GetAnalyticsPoliciesPolicy) GetLabel() string { + if g == nil { + return "" + } + return g.Label +} + +func (g *GetAnalyticsPoliciesPolicy) GetFlagged() int64 { + if g == nil { + return 0 + } + return g.Flagged +} + +func (g *GetAnalyticsPoliciesPolicy) GetPreviousFlagged() int64 { + if g == nil { + return 0 + } + return g.PreviousFlagged +} + +// GetAnalyticsPoliciesResponseBody - Successful response +type GetAnalyticsPoliciesResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsPoliciesRangeResponse `json:"range"` + // The channel the numbers are for. Null when they are for all channels. + ChannelID *string `json:"channelId"` + // The policy labels with the most flagged requests, largest first. + Policies []GetAnalyticsPoliciesPolicy `json:"policies"` +} + +func (g *GetAnalyticsPoliciesResponseBody) GetRange() GetAnalyticsPoliciesRangeResponse { + if g == nil { + return GetAnalyticsPoliciesRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsPoliciesResponseBody) GetChannelID() *string { + if g == nil { + return nil + } + return g.ChannelID +} + +func (g *GetAnalyticsPoliciesResponseBody) GetPolicies() []GetAnalyticsPoliciesPolicy { + if g == nil { + return []GetAnalyticsPoliciesPolicy{} + } + return g.Policies +} + +type GetAnalyticsPoliciesResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsPoliciesResponseBody +} + +func (g GetAnalyticsPoliciesResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsPoliciesResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsPoliciesResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsPoliciesResponse) GetObject() *GetAnalyticsPoliciesResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getanalyticssummary.go b/internal/sdk/models/operations/getanalyticssummary.go new file mode 100644 index 0000000..3fc55ec --- /dev/null +++ b/internal/sdk/models/operations/getanalyticssummary.go @@ -0,0 +1,406 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsSummaryQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsSummaryQueryParamRange string + +const ( + GetAnalyticsSummaryQueryParamRangeTwentyFourh GetAnalyticsSummaryQueryParamRange = "24h" + GetAnalyticsSummaryQueryParamRangeSevend GetAnalyticsSummaryQueryParamRange = "7d" + GetAnalyticsSummaryQueryParamRangeThirtyd GetAnalyticsSummaryQueryParamRange = "30d" + GetAnalyticsSummaryQueryParamRangeNinetyd GetAnalyticsSummaryQueryParamRange = "90d" +) + +func (e GetAnalyticsSummaryQueryParamRange) ToPointer() *GetAnalyticsSummaryQueryParamRange { + return &e +} +func (e *GetAnalyticsSummaryQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsSummaryQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsSummaryQueryParamRange: %v", v) + } +} + +type GetAnalyticsSummaryRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsSummaryQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + Channel *string `queryParam:"style=form,explode=true,name=channel"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsSummaryRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsSummaryRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsSummaryRequest) GetRange() *GetAnalyticsSummaryQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsSummaryRequest) GetChannel() *string { + if g == nil { + return nil + } + return g.Channel +} + +func (g *GetAnalyticsSummaryRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsSummaryRangeResponse - The time window of the numbers. +type GetAnalyticsSummaryRangeResponse string + +const ( + GetAnalyticsSummaryRangeResponseTwentyFourh GetAnalyticsSummaryRangeResponse = "24h" + GetAnalyticsSummaryRangeResponseSevend GetAnalyticsSummaryRangeResponse = "7d" + GetAnalyticsSummaryRangeResponseThirtyd GetAnalyticsSummaryRangeResponse = "30d" + GetAnalyticsSummaryRangeResponseNinetyd GetAnalyticsSummaryRangeResponse = "90d" +) + +func (e GetAnalyticsSummaryRangeResponse) ToPointer() *GetAnalyticsSummaryRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsSummaryRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +// Totals - Request counts for the window. A flagged request has one recommendation: allow, reject or review. +type Totals struct { + // Requests received. Every request counts, also repeats and edits of the same content. + Requests int64 `json:"requests"` + // Requests that were flagged. + Flagged int64 `json:"flagged"` + // The share of requests that were flagged: flagged / requests. 0 when there were no requests. + FlagRate float64 `json:"flagRate"` + // Requests with the recommendation "allow". Includes content that was not flagged. + Allowed int64 `json:"allowed"` + // Requests with the recommendation "reject". All of them were flagged. + Rejected int64 `json:"rejected"` + // Requests with the recommendation "review". This counts requests, not the deduplicated items in the review queue. + SentToReview int64 `json:"sentToReview"` + // Flagged requests that were settled without human review: rejected plus flaggedButAllowed. + HandledAutomatically int64 `json:"handledAutomatically"` + // Flagged requests that were allowed without human review. + FlaggedButAllowed int64 `json:"flaggedButAllowed"` + // Distinct authors that sent content in the window. Approximate. + DistinctAuthors int64 `json:"distinctAuthors"` +} + +func (t *Totals) GetRequests() int64 { + if t == nil { + return 0 + } + return t.Requests +} + +func (t *Totals) GetFlagged() int64 { + if t == nil { + return 0 + } + return t.Flagged +} + +func (t *Totals) GetFlagRate() float64 { + if t == nil { + return 0.0 + } + return t.FlagRate +} + +func (t *Totals) GetAllowed() int64 { + if t == nil { + return 0 + } + return t.Allowed +} + +func (t *Totals) GetRejected() int64 { + if t == nil { + return 0 + } + return t.Rejected +} + +func (t *Totals) GetSentToReview() int64 { + if t == nil { + return 0 + } + return t.SentToReview +} + +func (t *Totals) GetHandledAutomatically() int64 { + if t == nil { + return 0 + } + return t.HandledAutomatically +} + +func (t *Totals) GetFlaggedButAllowed() int64 { + if t == nil { + return 0 + } + return t.FlaggedButAllowed +} + +func (t *Totals) GetDistinctAuthors() int64 { + if t == nil { + return 0 + } + return t.DistinctAuthors +} + +// PreviousPeriod - The same counts for the window of equal length just before this one. Compare with `totals` to see the trend. +type PreviousPeriod struct { + // Requests received. Every request counts, also repeats and edits of the same content. + Requests int64 `json:"requests"` + // Requests that were flagged. + Flagged int64 `json:"flagged"` + // The share of requests that were flagged: flagged / requests. 0 when there were no requests. + FlagRate float64 `json:"flagRate"` + // Requests with the recommendation "allow". Includes content that was not flagged. + Allowed int64 `json:"allowed"` + // Requests with the recommendation "reject". All of them were flagged. + Rejected int64 `json:"rejected"` + // Requests with the recommendation "review". This counts requests, not the deduplicated items in the review queue. + SentToReview int64 `json:"sentToReview"` + // Flagged requests that were settled without human review: rejected plus flaggedButAllowed. + HandledAutomatically int64 `json:"handledAutomatically"` + // Flagged requests that were allowed without human review. + FlaggedButAllowed int64 `json:"flaggedButAllowed"` +} + +func (p *PreviousPeriod) GetRequests() int64 { + if p == nil { + return 0 + } + return p.Requests +} + +func (p *PreviousPeriod) GetFlagged() int64 { + if p == nil { + return 0 + } + return p.Flagged +} + +func (p *PreviousPeriod) GetFlagRate() float64 { + if p == nil { + return 0.0 + } + return p.FlagRate +} + +func (p *PreviousPeriod) GetAllowed() int64 { + if p == nil { + return 0 + } + return p.Allowed +} + +func (p *PreviousPeriod) GetRejected() int64 { + if p == nil { + return 0 + } + return p.Rejected +} + +func (p *PreviousPeriod) GetSentToReview() int64 { + if p == nil { + return 0 + } + return p.SentToReview +} + +func (p *PreviousPeriod) GetHandledAutomatically() int64 { + if p == nil { + return 0 + } + return p.HandledAutomatically +} + +func (p *PreviousPeriod) GetFlaggedButAllowed() int64 { + if p == nil { + return 0 + } + return p.FlaggedButAllowed +} + +type FlagRateSpike struct { + // How many times higher the recent flag rate is than the baseline, for example 2.4. + Multiple float64 `json:"multiple"` + // The flag rate in the last 2 days. + RecentFlagRate float64 `json:"recentFlagRate"` + // The flag rate in the 30 days before that. + BaselineFlagRate float64 `json:"baselineFlagRate"` + // Flagged requests in the last 2 days. + RecentFlagged int64 `json:"recentFlagged"` + // Requests in the last 2 days. + RecentRequests int64 `json:"recentRequests"` +} + +func (f *FlagRateSpike) GetMultiple() float64 { + if f == nil { + return 0.0 + } + return f.Multiple +} + +func (f *FlagRateSpike) GetRecentFlagRate() float64 { + if f == nil { + return 0.0 + } + return f.RecentFlagRate +} + +func (f *FlagRateSpike) GetBaselineFlagRate() float64 { + if f == nil { + return 0.0 + } + return f.BaselineFlagRate +} + +func (f *FlagRateSpike) GetRecentFlagged() int64 { + if f == nil { + return 0 + } + return f.RecentFlagged +} + +func (f *FlagRateSpike) GetRecentRequests() int64 { + if f == nil { + return 0 + } + return f.RecentRequests +} + +// GetAnalyticsSummaryResponseBody - Successful response +type GetAnalyticsSummaryResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsSummaryRangeResponse `json:"range"` + // The channel the numbers are for. Null when they are for all channels. + ChannelID *string `json:"channelId"` + // Request counts for the window. A flagged request has one recommendation: allow, reject or review. + Totals Totals `json:"totals"` + // The same counts for the window of equal length just before this one. Compare with `totals` to see the trend. + PreviousPeriod PreviousPeriod `json:"previousPeriod"` + // Items in the default review queue that nobody has reviewed yet, counted as the queue counts them (repeats count once). The queue looks back 30 days, so older items are not included. The range does not change it. + PendingReview int64 `json:"pendingReview"` + // Set when the flag rate of the last 2 days is at least 2 times the rate of the 30 days before, with at least 50 flagged requests. Null when there is no spike. Always for the full project: the range and channel do not change it. + FlagRateSpike *FlagRateSpike `json:"flagRateSpike"` +} + +func (g *GetAnalyticsSummaryResponseBody) GetRange() GetAnalyticsSummaryRangeResponse { + if g == nil { + return GetAnalyticsSummaryRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsSummaryResponseBody) GetChannelID() *string { + if g == nil { + return nil + } + return g.ChannelID +} + +func (g *GetAnalyticsSummaryResponseBody) GetTotals() Totals { + if g == nil { + return Totals{} + } + return g.Totals +} + +func (g *GetAnalyticsSummaryResponseBody) GetPreviousPeriod() PreviousPeriod { + if g == nil { + return PreviousPeriod{} + } + return g.PreviousPeriod +} + +func (g *GetAnalyticsSummaryResponseBody) GetPendingReview() int64 { + if g == nil { + return 0 + } + return g.PendingReview +} + +func (g *GetAnalyticsSummaryResponseBody) GetFlagRateSpike() *FlagRateSpike { + if g == nil { + return nil + } + return g.FlagRateSpike +} + +type GetAnalyticsSummaryResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsSummaryResponseBody +} + +func (g GetAnalyticsSummaryResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsSummaryResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsSummaryResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsSummaryResponse) GetObject() *GetAnalyticsSummaryResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getanalyticsvolumes.go b/internal/sdk/models/operations/getanalyticsvolumes.go new file mode 100644 index 0000000..417335c --- /dev/null +++ b/internal/sdk/models/operations/getanalyticsvolumes.go @@ -0,0 +1,250 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "encoding/json" + "fmt" + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +// GetAnalyticsVolumesQueryParamRange - The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. +type GetAnalyticsVolumesQueryParamRange string + +const ( + GetAnalyticsVolumesQueryParamRangeTwentyFourh GetAnalyticsVolumesQueryParamRange = "24h" + GetAnalyticsVolumesQueryParamRangeSevend GetAnalyticsVolumesQueryParamRange = "7d" + GetAnalyticsVolumesQueryParamRangeThirtyd GetAnalyticsVolumesQueryParamRange = "30d" + GetAnalyticsVolumesQueryParamRangeNinetyd GetAnalyticsVolumesQueryParamRange = "90d" +) + +func (e GetAnalyticsVolumesQueryParamRange) ToPointer() *GetAnalyticsVolumesQueryParamRange { + return &e +} +func (e *GetAnalyticsVolumesQueryParamRange) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "24h": + fallthrough + case "7d": + fallthrough + case "30d": + fallthrough + case "90d": + *e = GetAnalyticsVolumesQueryParamRange(v) + return nil + default: + return fmt.Errorf("invalid value for GetAnalyticsVolumesQueryParamRange: %v", v) + } +} + +type GetAnalyticsVolumesRequest struct { + // The time window. `24h` is the last 24 hours, in hourly buckets. `7d`, `30d` and `90d` are whole UTC days that end with today, in daily buckets. + Range *GetAnalyticsVolumesQueryParamRange `default:"30d" queryParam:"style=form,explode=true,name=range"` + Channel *string `queryParam:"style=form,explode=true,name=channel"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (g GetAnalyticsVolumesRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsVolumesRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsVolumesRequest) GetRange() *GetAnalyticsVolumesQueryParamRange { + if g == nil { + return nil + } + return g.Range +} + +func (g *GetAnalyticsVolumesRequest) GetChannel() *string { + if g == nil { + return nil + } + return g.Channel +} + +func (g *GetAnalyticsVolumesRequest) GetProjectID() *string { + if g == nil { + return nil + } + return g.ProjectID +} + +// GetAnalyticsVolumesRangeResponse - The time window of the numbers. +type GetAnalyticsVolumesRangeResponse string + +const ( + GetAnalyticsVolumesRangeResponseTwentyFourh GetAnalyticsVolumesRangeResponse = "24h" + GetAnalyticsVolumesRangeResponseSevend GetAnalyticsVolumesRangeResponse = "7d" + GetAnalyticsVolumesRangeResponseThirtyd GetAnalyticsVolumesRangeResponse = "30d" + GetAnalyticsVolumesRangeResponseNinetyd GetAnalyticsVolumesRangeResponse = "90d" +) + +func (e GetAnalyticsVolumesRangeResponse) ToPointer() *GetAnalyticsVolumesRangeResponse { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *GetAnalyticsVolumesRangeResponse) IsExact() bool { + if e != nil { + switch *e { + case "24h", "7d", "30d", "90d": + return true + } + } + return false +} + +// Bucket - The size of each entry in `volumes`. +type Bucket string + +const ( + BucketHour Bucket = "hour" + BucketDay Bucket = "day" +) + +func (e Bucket) ToPointer() *Bucket { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *Bucket) IsExact() bool { + if e != nil { + switch *e { + case "hour", "day": + return true + } + } + return false +} + +type Volume struct { + // Start of the bucket, as an ISO 8601 timestamp in UTC. + Start string `json:"start"` + // Requests received in the bucket. + Requests int64 `json:"requests"` + // Requests flagged in the bucket. + Flagged int64 `json:"flagged"` + // Requests with the recommendation "reject" in the bucket. + Rejected int64 `json:"rejected"` + // Requests with the recommendation "review" in the bucket. + SentToReview int64 `json:"sentToReview"` +} + +func (v *Volume) GetStart() string { + if v == nil { + return "" + } + return v.Start +} + +func (v *Volume) GetRequests() int64 { + if v == nil { + return 0 + } + return v.Requests +} + +func (v *Volume) GetFlagged() int64 { + if v == nil { + return 0 + } + return v.Flagged +} + +func (v *Volume) GetRejected() int64 { + if v == nil { + return 0 + } + return v.Rejected +} + +func (v *Volume) GetSentToReview() int64 { + if v == nil { + return 0 + } + return v.SentToReview +} + +// GetAnalyticsVolumesResponseBody - Successful response +type GetAnalyticsVolumesResponseBody struct { + // The time window of the numbers. + Range GetAnalyticsVolumesRangeResponse `json:"range"` + // The channel the numbers are for. Null when they are for all channels. + ChannelID *string `json:"channelId"` + // The size of each entry in `volumes`. + Bucket Bucket `json:"bucket"` + // Counts over time, oldest first. Buckets with no requests are included with zeros. + Volumes []Volume `json:"volumes"` +} + +func (g *GetAnalyticsVolumesResponseBody) GetRange() GetAnalyticsVolumesRangeResponse { + if g == nil { + return GetAnalyticsVolumesRangeResponse("") + } + return g.Range +} + +func (g *GetAnalyticsVolumesResponseBody) GetChannelID() *string { + if g == nil { + return nil + } + return g.ChannelID +} + +func (g *GetAnalyticsVolumesResponseBody) GetBucket() Bucket { + if g == nil { + return Bucket("") + } + return g.Bucket +} + +func (g *GetAnalyticsVolumesResponseBody) GetVolumes() []Volume { + if g == nil { + return []Volume{} + } + return g.Volumes +} + +type GetAnalyticsVolumesResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *GetAnalyticsVolumesResponseBody +} + +func (g GetAnalyticsVolumesResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(g, "", false) +} + +func (g *GetAnalyticsVolumesResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &g, "", false, nil); err != nil { + return err + } + return nil +} + +func (g *GetAnalyticsVolumesResponse) GetHTTPMeta() components.HTTPMetadata { + if g == nil { + return components.HTTPMetadata{} + } + return g.HTTPMeta +} + +func (g *GetAnalyticsVolumesResponse) GetObject() *GetAnalyticsVolumesResponseBody { + if g == nil { + return nil + } + return g.Object +} diff --git a/internal/sdk/models/operations/getqueue.go b/internal/sdk/models/operations/getqueue.go index 69f5146..f96e850 100644 --- a/internal/sdk/models/operations/getqueue.go +++ b/internal/sdk/models/operations/getqueue.go @@ -4,12 +4,11 @@ package operations import ( "github.com/moderation-api/cli/internal/sdk/models/components" - "github.com/moderation-api/cli/internal/sdk/optionalnullable" "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" ) type GetQueueRequest struct { - // The queue ID + // The queue ID. ID string `pathParam:"style=simple,explode=false,name=id"` // 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. ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` @@ -29,562 +28,14 @@ func (g *GetQueueRequest) GetProjectID() *string { return g.ProjectID } -type IsFlagged string - -const ( - IsFlaggedAll IsFlagged = "ALL" - IsFlaggedFlagged IsFlagged = "FLAGGED" - IsFlaggedNotFlagged IsFlagged = "NOT_FLAGGED" - IsFlaggedShadowFlagged IsFlagged = "SHADOW_FLAGGED" -) - -func (e IsFlagged) ToPointer() *IsFlagged { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *IsFlagged) IsExact() bool { - if e != nil { - switch *e { - case "ALL", "FLAGGED", "NOT_FLAGGED", "SHADOW_FLAGGED": - return true - } - } - return false -} - -type CasebookAnswer string - -const ( - CasebookAnswerAll CasebookAnswer = "ALL" - CasebookAnswerAllowed CasebookAnswer = "ALLOWED" - CasebookAnswerRejected CasebookAnswer = "REJECTED" - CasebookAnswerNoAnswer CasebookAnswer = "NO_ANSWER" -) - -func (e CasebookAnswer) ToPointer() *CasebookAnswer { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *CasebookAnswer) IsExact() bool { - if e != nil { - switch *e { - case "ALL", "ALLOWED", "REJECTED", "NO_ANSWER": - return true - } - } - return false -} - -type CasebookAgreement string - -const ( - CasebookAgreementAll CasebookAgreement = "ALL" - CasebookAgreementOverruled CasebookAgreement = "OVERRULED" - CasebookAgreementAgreed CasebookAgreement = "AGREED" -) - -func (e CasebookAgreement) ToPointer() *CasebookAgreement { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *CasebookAgreement) IsExact() bool { - if e != nil { - switch *e { - case "ALL", "OVERRULED", "AGREED": - return true - } - } - return false -} - -type GetQueueType string - -const ( - GetQueueTypeFlagged GetQueueType = "FLAGGED" - GetQueueTypeNotFlagged GetQueueType = "NOT_FLAGGED" - GetQueueTypeThresholds GetQueueType = "THRESHOLDS" - GetQueueTypeMatched GetQueueType = "MATCHED" -) - -func (e GetQueueType) ToPointer() *GetQueueType { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *GetQueueType) IsExact() bool { - if e != nil { - switch *e { - case "FLAGGED", "NOT_FLAGGED", "THRESHOLDS", "MATCHED": - return true - } - } - return false -} - -type FilterLabel struct { - Label string `json:"label"` - Type GetQueueType `json:"type"` - MinThreshold optionalnullable.OptionalNullable[float64] `json:"minThreshold,omitzero"` - MaxThreshold optionalnullable.OptionalNullable[float64] `json:"maxThreshold,omitzero"` -} - -func (f *FilterLabel) GetLabel() string { - if f == nil { - return "" - } - return f.Label -} - -func (f *FilterLabel) GetType() GetQueueType { - if f == nil { - return GetQueueType("") - } - return f.Type -} - -func (f *FilterLabel) GetMinThreshold() optionalnullable.OptionalNullable[float64] { - if f == nil { - return nil - } - return f.MinThreshold -} - -func (f *FilterLabel) GetMaxThreshold() optionalnullable.OptionalNullable[float64] { - if f == nil { - return nil - } - return f.MaxThreshold -} - -type MetadataFilter struct { - Key string `json:"key"` - Value string `json:"value"` -} - -func (m *MetadataFilter) GetKey() string { - if m == nil { - return "" - } - return m.Key -} - -func (m *MetadataFilter) GetValue() string { - if m == nil { - return "" - } - return m.Value -} - -type GetQueueContentType string - -const ( - GetQueueContentTypeProfile GetQueueContentType = "profile" - GetQueueContentTypeMessage GetQueueContentType = "message" - GetQueueContentTypePost GetQueueContentType = "post" - GetQueueContentTypeComment GetQueueContentType = "comment" - GetQueueContentTypeEvent GetQueueContentType = "event" - GetQueueContentTypeProduct GetQueueContentType = "product" - GetQueueContentTypeReview GetQueueContentType = "review" - GetQueueContentTypeVoice GetQueueContentType = "voice" - GetQueueContentTypeOther GetQueueContentType = "other" -) - -func (e GetQueueContentType) ToPointer() *GetQueueContentType { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *GetQueueContentType) IsExact() bool { - if e != nil { - switch *e { - case "profile", "message", "post", "comment", "event", "product", "review", "voice", "other": - return true - } - } - return false -} - -type MediaType string - -const ( - MediaTypeText MediaType = "text" - MediaTypeImage MediaType = "image" - MediaTypeVideo MediaType = "video" - MediaTypeObject MediaType = "object" - MediaTypeAudio MediaType = "audio" -) - -func (e MediaType) ToPointer() *MediaType { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *MediaType) IsExact() bool { - if e != nil { - switch *e { - case "text", "image", "video", "object", "audio": - return true - } - } - return false -} - -type GetQueueRecommendationAction string - -const ( - GetQueueRecommendationActionReview GetQueueRecommendationAction = "review" - GetQueueRecommendationActionAllow GetQueueRecommendationAction = "allow" - GetQueueRecommendationActionReject GetQueueRecommendationAction = "reject" -) - -func (e GetQueueRecommendationAction) ToPointer() *GetQueueRecommendationAction { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *GetQueueRecommendationAction) IsExact() bool { - if e != nil { - switch *e { - case "review", "allow", "reject": - return true - } - } - return false -} - -type WithinUnit string - -const ( - WithinUnitMinutes WithinUnit = "MINUTES" - WithinUnitHours WithinUnit = "HOURS" - WithinUnitDays WithinUnit = "DAYS" - WithinUnitWeeks WithinUnit = "WEEKS" - WithinUnitMonths WithinUnit = "MONTHS" - WithinUnitYears WithinUnit = "YEARS" -) - -func (e WithinUnit) ToPointer() *WithinUnit { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *WithinUnit) IsExact() bool { - if e != nil { - switch *e { - case "MINUTES", "HOURS", "DAYS", "WEEKS", "MONTHS", "YEARS": - return true - } - } - return false -} - -type CheckStatus string - -const ( - CheckStatusAll CheckStatus = "all" - CheckStatusChecked CheckStatus = "checked" - CheckStatusUnchecked CheckStatus = "unchecked" -) - -func (e CheckStatus) ToPointer() *CheckStatus { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *CheckStatus) IsExact() bool { - if e != nil { - switch *e { - case "all", "checked", "unchecked": - return true - } - } - return false -} - -type Filter struct { - IsFlagged *IsFlagged `json:"isFlagged,omitzero"` - CasebookAnswer *CasebookAnswer `json:"casebookAnswer,omitzero"` - CasebookAgreement *CasebookAgreement `json:"casebookAgreement,omitzero"` - Labels []string `json:"labels,omitzero"` - FilterLabels []FilterLabel `json:"filterLabels,omitzero"` - MetadataFilters []MetadataFilter `json:"metadataFilters,omitzero"` - FilteredChannelIds []string `json:"filteredChannelIds,omitzero"` - FilteredActionIds []string `json:"filteredActionIds,omitzero"` - Languages []string `json:"languages,omitzero"` - ContentTypes []GetQueueContentType `json:"contentTypes,omitzero"` - MediaTypes []MediaType `json:"mediaTypes,omitzero"` - AuthorTrustLevels []int64 `json:"authorTrustLevels,omitzero"` - RecommendationActions []GetQueueRecommendationAction `json:"recommendationActions,omitzero"` - MinSeverity *int64 `json:"minSeverity,omitzero"` - MaxSeverity *int64 `json:"maxSeverity,omitzero"` - AfterDate *string `json:"afterDate,omitzero"` - BeforeDate *string `json:"beforeDate,omitzero"` - Within *float64 `json:"within,omitzero"` - WithinUnit *WithinUnit `json:"withinUnit,omitzero"` - ClearDateWindow *bool `json:"clearDateWindow,omitzero"` - CheckStatus optionalnullable.OptionalNullable[CheckStatus] `json:"checkStatus,omitzero"` - AuthorID *string `json:"authorID,omitzero"` - ContentID *string `json:"contentID,omitzero"` - ConversationIds []*string `json:"conversationIds,omitzero"` - Search []string `json:"search,omitzero"` -} - -func (f Filter) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(f, "", false) -} - -func (f *Filter) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &f, "", false, nil); err != nil { - return err - } - return nil -} - -func (f *Filter) GetIsFlagged() *IsFlagged { - if f == nil { - return nil - } - return f.IsFlagged -} - -func (f *Filter) GetCasebookAnswer() *CasebookAnswer { - if f == nil { - return nil - } - return f.CasebookAnswer -} - -func (f *Filter) GetCasebookAgreement() *CasebookAgreement { - if f == nil { - return nil - } - return f.CasebookAgreement -} - -func (f *Filter) GetLabels() []string { - if f == nil { - return nil - } - return f.Labels -} - -func (f *Filter) GetFilterLabels() []FilterLabel { - if f == nil { - return nil - } - return f.FilterLabels -} - -func (f *Filter) GetMetadataFilters() []MetadataFilter { - if f == nil { - return nil - } - return f.MetadataFilters -} - -func (f *Filter) GetFilteredChannelIds() []string { - if f == nil { - return nil - } - return f.FilteredChannelIds -} - -func (f *Filter) GetFilteredActionIds() []string { - if f == nil { - return nil - } - return f.FilteredActionIds -} - -func (f *Filter) GetLanguages() []string { - if f == nil { - return nil - } - return f.Languages -} - -func (f *Filter) GetContentTypes() []GetQueueContentType { - if f == nil { - return nil - } - return f.ContentTypes -} - -func (f *Filter) GetMediaTypes() []MediaType { - if f == nil { - return nil - } - return f.MediaTypes -} - -func (f *Filter) GetAuthorTrustLevels() []int64 { - if f == nil { - return nil - } - return f.AuthorTrustLevels -} - -func (f *Filter) GetRecommendationActions() []GetQueueRecommendationAction { - if f == nil { - return nil - } - return f.RecommendationActions -} - -func (f *Filter) GetMinSeverity() *int64 { - if f == nil { - return nil - } - return f.MinSeverity -} - -func (f *Filter) GetMaxSeverity() *int64 { - if f == nil { - return nil - } - return f.MaxSeverity -} - -func (f *Filter) GetAfterDate() *string { - if f == nil { - return nil - } - return f.AfterDate -} - -func (f *Filter) GetBeforeDate() *string { - if f == nil { - return nil - } - return f.BeforeDate -} - -func (f *Filter) GetWithin() *float64 { - if f == nil { - return nil - } - return f.Within -} - -func (f *Filter) GetWithinUnit() *WithinUnit { - if f == nil { - return nil - } - return f.WithinUnit -} - -func (f *Filter) GetClearDateWindow() *bool { - if f == nil { - return nil - } - return f.ClearDateWindow -} - -func (f *Filter) GetCheckStatus() optionalnullable.OptionalNullable[CheckStatus] { - if f == nil { - return nil - } - return f.CheckStatus -} - -func (f *Filter) GetAuthorID() *string { - if f == nil { - return nil - } - return f.AuthorID -} - -func (f *Filter) GetContentID() *string { - if f == nil { - return nil - } - return f.ContentID -} - -func (f *Filter) GetConversationIds() []*string { - if f == nil { - return nil - } - return f.ConversationIds -} - -func (f *Filter) GetSearch() []string { - if f == nil { - return nil - } - return f.Search -} - -type Queue struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Filter Filter `json:"filter"` - UnresolvedItemsCount float64 `json:"unresolvedItemsCount"` - ResolvedItemsCount float64 `json:"resolvedItemsCount"` - TotalItemsCount float64 `json:"totalItemsCount"` -} - -func (q *Queue) GetID() string { - if q == nil { - return "" - } - return q.ID -} - -func (q *Queue) GetName() string { - if q == nil { - return "" - } - return q.Name -} - -func (q *Queue) GetDescription() string { - if q == nil { - return "" - } - return q.Description -} - -func (q *Queue) GetFilter() Filter { - if q == nil { - return Filter{} - } - return q.Filter -} - -func (q *Queue) GetUnresolvedItemsCount() float64 { - if q == nil { - return 0.0 - } - return q.UnresolvedItemsCount -} - -func (q *Queue) GetResolvedItemsCount() float64 { - if q == nil { - return 0.0 - } - return q.ResolvedItemsCount -} - -func (q *Queue) GetTotalItemsCount() float64 { - if q == nil { - return 0.0 - } - return q.TotalItemsCount -} - // GetQueueResponseBody - Successful response type GetQueueResponseBody struct { - Queue Queue `json:"queue"` + Queue components.QueueWithCounts `json:"queue"` } -func (g *GetQueueResponseBody) GetQueue() Queue { +func (g *GetQueueResponseBody) GetQueue() components.QueueWithCounts { if g == nil { - return Queue{} + return components.QueueWithCounts{} } return g.Queue } diff --git a/internal/sdk/models/operations/getqueuestats.go b/internal/sdk/models/operations/getqueuestats.go index 05490cc..1cb49ee 100644 --- a/internal/sdk/models/operations/getqueuestats.go +++ b/internal/sdk/models/operations/getqueuestats.go @@ -8,7 +8,7 @@ import ( ) type GetQueueStatsRequest struct { - // The queue ID + // The queue ID. ID string `pathParam:"style=simple,explode=false,name=id"` WithinDays *int64 `default:"30" queryParam:"style=form,explode=true,name=withinDays"` // 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. @@ -48,166 +48,61 @@ func (g *GetQueueStatsRequest) GetProjectID() *string { } type ReviewStats struct { - // Average time in milliseconds to review an item - AverageTimeToReview float64 `json:"averageTimeToReview"` - // Total number of items reviewed - TotalReviewed float64 `json:"totalReviewed"` - // Total number of items pending review - TotalPending float64 `json:"totalPending"` + // Number of items resolved in this queue in the period. + TotalReviewed int64 `json:"totalReviewed"` + // Number of items submitted in the period that wait for review now. + TotalPending int64 `json:"totalPending"` } -func (r *ReviewStats) GetAverageTimeToReview() float64 { +func (r *ReviewStats) GetTotalReviewed() int64 { if r == nil { - return 0.0 - } - return r.AverageTimeToReview -} - -func (r *ReviewStats) GetTotalReviewed() float64 { - if r == nil { - return 0.0 + return 0 } return r.TotalReviewed } -func (r *ReviewStats) GetTotalPending() float64 { +func (r *ReviewStats) GetTotalPending() int64 { if r == nil { - return 0.0 + return 0 } return r.TotalPending } -type ActionStat struct { - // ID of the moderation action - ActionID string `json:"actionId"` - // Name of the moderation action - ActionName string `json:"actionName"` - // Number of times this action was taken - Count float64 `json:"count"` - // Percentage this action represents of all actions - PercentageOfTotal float64 `json:"percentageOfTotal"` -} - -func (a *ActionStat) GetActionID() string { - if a == nil { - return "" - } - return a.ActionID -} - -func (a *ActionStat) GetActionName() string { - if a == nil { - return "" - } - return a.ActionName -} - -func (a *ActionStat) GetCount() float64 { - if a == nil { - return 0.0 - } - return a.Count -} - -func (a *ActionStat) GetPercentageOfTotal() float64 { - if a == nil { - return 0.0 - } - return a.PercentageOfTotal -} - -type TopAction struct { - // Most used action by this reviewer - ActionID string `json:"actionId"` - // Name of the most used action - ActionName string `json:"actionName"` - // Number of times this action was used - Count float64 `json:"count"` -} - -func (t *TopAction) GetActionID() string { - if t == nil { - return "" - } - return t.ActionID -} - -func (t *TopAction) GetActionName() string { - if t == nil { - return "" - } - return t.ActionName -} - -func (t *TopAction) GetCount() float64 { - if t == nil { - return 0.0 - } - return t.Count -} - type TopReviewer struct { - // ID of the reviewer - UserID string `json:"userId"` - // Name of the reviewer - Name string `json:"name"` - // Number of items reviewed - ReviewCount float64 `json:"reviewCount"` - // Average review time in milliseconds - AverageTimePerReview float64 `json:"averageTimePerReview"` - // Optional accuracy score based on review quality metrics - AccuracyScore *float64 `json:"accuracyScore,omitzero"` - // Most common actions taken by this reviewer - TopActions []TopAction `json:"topActions"` + // The user ID of the reviewer. `null` for items resolved with an API key. + UserID *string `json:"userId"` + // The name of the reviewer. + Name *string `json:"name"` + // Number of items the reviewer resolved in the period. + ReviewCount int64 `json:"reviewCount"` } -func (t *TopReviewer) GetUserID() string { +func (t *TopReviewer) GetUserID() *string { if t == nil { - return "" + return nil } return t.UserID } -func (t *TopReviewer) GetName() string { +func (t *TopReviewer) GetName() *string { if t == nil { - return "" + return nil } return t.Name } -func (t *TopReviewer) GetReviewCount() float64 { +func (t *TopReviewer) GetReviewCount() int64 { if t == nil { - return 0.0 + return 0 } return t.ReviewCount } -func (t *TopReviewer) GetAverageTimePerReview() float64 { - if t == nil { - return 0.0 - } - return t.AverageTimePerReview -} - -func (t *TopReviewer) GetAccuracyScore() *float64 { - if t == nil { - return nil - } - return t.AccuracyScore -} - -func (t *TopReviewer) GetTopActions() []TopAction { - if t == nil { - return []TopAction{} - } - return t.TopActions -} - type DailyReviewCount struct { - // Date in YYYY-MM-DD format + // Date in YYYY-MM-DD format (UTC). Date string `json:"date"` - // Number of reviews on this date - Count float64 `json:"count"` + // Number of items resolved. + Count int64 `json:"count"` } func (d *DailyReviewCount) GetDate() string { @@ -217,37 +112,16 @@ func (d *DailyReviewCount) GetDate() string { return d.Date } -func (d *DailyReviewCount) GetCount() float64 { +func (d *DailyReviewCount) GetCount() int64 { if d == nil { - return 0.0 + return 0 } return d.Count } -type FlaggedContentTrend struct { - // Content flag/label - Label string `json:"label"` - // Trend indicator (-1 to 1) showing if this type of flagged content is increasing or decreasing - Trend float64 `json:"trend"` -} - -func (f *FlaggedContentTrend) GetLabel() string { - if f == nil { - return "" - } - return f.Label -} - -func (f *FlaggedContentTrend) GetTrend() float64 { - if f == nil { - return 0.0 - } - return f.Trend -} - type Trends struct { - DailyReviewCounts []DailyReviewCount `json:"dailyReviewCounts"` - FlaggedContentTrends []FlaggedContentTrend `json:"flaggedContentTrends"` + // Resolved items per day. Days with no reviews are left out. + DailyReviewCounts []DailyReviewCount `json:"dailyReviewCounts"` } func (t *Trends) GetDailyReviewCounts() []DailyReviewCount { @@ -257,18 +131,10 @@ func (t *Trends) GetDailyReviewCounts() []DailyReviewCount { return t.DailyReviewCounts } -func (t *Trends) GetFlaggedContentTrends() []FlaggedContentTrend { - if t == nil { - return []FlaggedContentTrend{} - } - return t.FlaggedContentTrends -} - // GetQueueStatsResponseBody - Successful response type GetQueueStatsResponseBody struct { - ReviewStats ReviewStats `json:"reviewStats"` - ActionStats []ActionStat `json:"actionStats"` - // List of top reviewers and their statistics + ReviewStats ReviewStats `json:"reviewStats"` + // Reviewers with the most resolved items, most first. Up to 10. TopReviewers []TopReviewer `json:"topReviewers"` Trends Trends `json:"trends"` } @@ -280,13 +146,6 @@ func (g *GetQueueStatsResponseBody) GetReviewStats() ReviewStats { return g.ReviewStats } -func (g *GetQueueStatsResponseBody) GetActionStats() []ActionStat { - if g == nil { - return []ActionStat{} - } - return g.ActionStats -} - func (g *GetQueueStatsResponseBody) GetTopReviewers() []TopReviewer { if g == nil { return []TopReviewer{} diff --git a/internal/sdk/models/operations/listauthors.go b/internal/sdk/models/operations/listauthors.go index 8ea4b78..f78f4c4 100644 --- a/internal/sdk/models/operations/listauthors.go +++ b/internal/sdk/models/operations/listauthors.go @@ -14,7 +14,6 @@ type SortBy string const ( SortByTrustLevel SortBy = "trustLevel" SortByViolationCount SortBy = "violationCount" - SortByReportCount SortBy = "reportCount" SortByMemberSince SortBy = "memberSince" SortByLastActive SortBy = "lastActive" SortByContentCount SortBy = "contentCount" @@ -35,8 +34,6 @@ func (e *SortBy) UnmarshalJSON(data []byte) error { fallthrough case "violationCount": fallthrough - case "reportCount": - fallthrough case "memberSince": fallthrough case "lastActive": @@ -53,18 +50,18 @@ func (e *SortBy) UnmarshalJSON(data []byte) error { } } -// ListAuthorsSortDirection - Sort direction -type ListAuthorsSortDirection string +// SortDirection - Sort direction +type SortDirection string const ( - ListAuthorsSortDirectionAsc ListAuthorsSortDirection = "asc" - ListAuthorsSortDirectionDesc ListAuthorsSortDirection = "desc" + SortDirectionAsc SortDirection = "asc" + SortDirectionDesc SortDirection = "desc" ) -func (e ListAuthorsSortDirection) ToPointer() *ListAuthorsSortDirection { +func (e SortDirection) ToPointer() *SortDirection { return &e } -func (e *ListAuthorsSortDirection) UnmarshalJSON(data []byte) error { +func (e *SortDirection) UnmarshalJSON(data []byte) error { var v string if err := json.Unmarshal(data, &v); err != nil { return err @@ -73,10 +70,10 @@ func (e *ListAuthorsSortDirection) UnmarshalJSON(data []byte) error { case "asc": fallthrough case "desc": - *e = ListAuthorsSortDirection(v) + *e = SortDirection(v) return nil default: - return fmt.Errorf("invalid value for ListAuthorsSortDirection: %v", v) + return fmt.Errorf("invalid value for SortDirection: %v", v) } } @@ -85,10 +82,9 @@ type ListAuthorsRequest struct { PageSize *int64 `default:"20" queryParam:"style=form,explode=true,name=pageSize"` SortBy *SortBy `queryParam:"style=form,explode=true,name=sortBy"` // Sort direction - SortDirection *ListAuthorsSortDirection `default:"desc" queryParam:"style=form,explode=true,name=sortDirection"` - MemberSinceDate *string `queryParam:"style=form,explode=true,name=memberSinceDate"` - LastActiveDate *string `queryParam:"style=form,explode=true,name=lastActiveDate"` - ContentTypes []string `queryParam:"style=form,explode=true,name=contentTypes"` + SortDirection *SortDirection `default:"desc" queryParam:"style=form,explode=true,name=sortDirection"` + MemberSinceDate *string `queryParam:"style=form,explode=true,name=memberSinceDate"` + LastActiveDate *string `queryParam:"style=form,explode=true,name=lastActiveDate"` // 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. ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` } @@ -125,7 +121,7 @@ func (l *ListAuthorsRequest) GetSortBy() *SortBy { return l.SortBy } -func (l *ListAuthorsRequest) GetSortDirection() *ListAuthorsSortDirection { +func (l *ListAuthorsRequest) GetSortDirection() *SortDirection { if l == nil { return nil } @@ -146,13 +142,6 @@ func (l *ListAuthorsRequest) GetLastActiveDate() *string { return l.LastActiveDate } -func (l *ListAuthorsRequest) GetContentTypes() []string { - if l == nil { - return nil - } - return l.ContentTypes -} - func (l *ListAuthorsRequest) GetProjectID() *string { if l == nil { return nil diff --git a/internal/sdk/models/operations/listqueueitems.go b/internal/sdk/models/operations/listqueueitems.go index fc1de25..2f35ff0 100644 --- a/internal/sdk/models/operations/listqueueitems.go +++ b/internal/sdk/models/operations/listqueueitems.go @@ -9,77 +9,51 @@ import ( "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" ) -type SortField string +type Sort string const ( - SortFieldCreatedAt SortField = "createdAt" - SortFieldSeverity SortField = "severity" - SortFieldReviewedAt SortField = "reviewedAt" + SortFifo Sort = "FIFO" + SortLifo Sort = "LIFO" + SortRandom Sort = "RANDOM" + SortSeverityDesc Sort = "SEVERITY_DESC" ) -func (e SortField) ToPointer() *SortField { +func (e Sort) ToPointer() *Sort { return &e } -func (e *SortField) UnmarshalJSON(data []byte) error { +func (e *Sort) UnmarshalJSON(data []byte) error { var v string if err := json.Unmarshal(data, &v); err != nil { return err } switch v { - case "createdAt": + case "FIFO": fallthrough - case "severity": + case "LIFO": fallthrough - case "reviewedAt": - *e = SortField(v) - return nil - default: - return fmt.Errorf("invalid value for SortField: %v", v) - } -} - -// ListQueueItemsSortDirection - Sort direction -type ListQueueItemsSortDirection string - -const ( - ListQueueItemsSortDirectionAsc ListQueueItemsSortDirection = "asc" - ListQueueItemsSortDirectionDesc ListQueueItemsSortDirection = "desc" -) - -func (e ListQueueItemsSortDirection) ToPointer() *ListQueueItemsSortDirection { - return &e -} -func (e *ListQueueItemsSortDirection) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "asc": + case "RANDOM": fallthrough - case "desc": - *e = ListQueueItemsSortDirection(v) + case "SEVERITY_DESC": + *e = Sort(v) return nil default: - return fmt.Errorf("invalid value for ListQueueItemsSortDirection: %v", v) + return fmt.Errorf("invalid value for Sort: %v", v) } } type ListQueueItemsRequest struct { - // The queue ID - ID string `pathParam:"style=simple,explode=false,name=id"` - Page *int64 `default:"1" queryParam:"style=form,explode=true,name=page"` - PageSize *int64 `default:"20" queryParam:"style=form,explode=true,name=pageSize"` - SortField *SortField `queryParam:"style=form,explode=true,name=sortField"` - // Sort direction - SortDirection *ListQueueItemsSortDirection `default:"desc" queryParam:"style=form,explode=true,name=sortDirection"` - ConversationIds []string `queryParam:"style=form,explode=true,name=conversationIds"` - Labels []string `queryParam:"style=form,explode=true,name=labels"` - AfterDate *string `queryParam:"style=form,explode=true,name=afterDate"` - BeforeDate *string `queryParam:"style=form,explode=true,name=beforeDate"` - IncludeResolved *bool `queryParam:"style=form,explode=true,name=includeResolved"` - AuthorID *string `queryParam:"style=form,explode=true,name=authorId"` - FilteredActionIds []string `queryParam:"style=form,explode=true,name=filteredActionIds"` + // The queue ID. + ID string `pathParam:"style=simple,explode=false,name=id"` + Page *int64 `default:"1" queryParam:"style=form,explode=true,name=page"` + PageSize *int64 `default:"20" queryParam:"style=form,explode=true,name=pageSize"` + Sort *Sort `queryParam:"style=form,explode=true,name=sort"` + IncludeResolved *bool `queryParam:"style=form,explode=true,name=includeResolved"` + ConversationIds []string `queryParam:"style=form,explode=true,name=conversationIds"` + Labels []string `queryParam:"style=form,explode=true,name=labels"` + AuthorID *string `queryParam:"style=form,explode=true,name=authorId"` + FilteredActionIds []string `queryParam:"style=form,explode=true,name=filteredActionIds"` + AfterDate *string `queryParam:"style=form,explode=true,name=afterDate"` + BeforeDate *string `queryParam:"style=form,explode=true,name=beforeDate"` // 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. ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` } @@ -116,18 +90,18 @@ func (l *ListQueueItemsRequest) GetPageSize() *int64 { return l.PageSize } -func (l *ListQueueItemsRequest) GetSortField() *SortField { +func (l *ListQueueItemsRequest) GetSort() *Sort { if l == nil { return nil } - return l.SortField + return l.Sort } -func (l *ListQueueItemsRequest) GetSortDirection() *ListQueueItemsSortDirection { +func (l *ListQueueItemsRequest) GetIncludeResolved() *bool { if l == nil { return nil } - return l.SortDirection + return l.IncludeResolved } func (l *ListQueueItemsRequest) GetConversationIds() []string { @@ -144,27 +118,6 @@ func (l *ListQueueItemsRequest) GetLabels() []string { return l.Labels } -func (l *ListQueueItemsRequest) GetAfterDate() *string { - if l == nil { - return nil - } - return l.AfterDate -} - -func (l *ListQueueItemsRequest) GetBeforeDate() *string { - if l == nil { - return nil - } - return l.BeforeDate -} - -func (l *ListQueueItemsRequest) GetIncludeResolved() *bool { - if l == nil { - return nil - } - return l.IncludeResolved -} - func (l *ListQueueItemsRequest) GetAuthorID() *string { if l == nil { return nil @@ -179,237 +132,38 @@ func (l *ListQueueItemsRequest) GetFilteredActionIds() []string { return l.FilteredActionIds } -func (l *ListQueueItemsRequest) GetProjectID() *string { - if l == nil { - return nil - } - return l.ProjectID -} - -type ListQueueItemsLabel struct { - // Label name - Label string `json:"label"` - // Confidence score of the label - Score float64 `json:"score"` - // Whether this label caused a flag - Flagged bool `json:"flagged"` -} - -func (l *ListQueueItemsLabel) GetLabel() string { - if l == nil { - return "" - } - return l.Label -} - -func (l *ListQueueItemsLabel) GetScore() float64 { - if l == nil { - return 0.0 - } - return l.Score -} - -func (l *ListQueueItemsLabel) GetFlagged() bool { - if l == nil { - return false - } - return l.Flagged -} - -type ListQueueItemsAction struct { - // Action ID - ID string `json:"id"` - // Action name - Name string `json:"name"` - // Action comment - Comment *string `json:"comment,omitzero"` - // Unix timestamp of when the action was taken - Timestamp float64 `json:"timestamp"` - // Moderator userID - Reviewer *string `json:"reviewer,omitzero"` -} - -func (l *ListQueueItemsAction) GetID() string { - if l == nil { - return "" - } - return l.ID -} - -func (l *ListQueueItemsAction) GetName() string { - if l == nil { - return "" - } - return l.Name -} - -func (l *ListQueueItemsAction) GetComment() *string { - if l == nil { - return nil - } - return l.Comment -} - -func (l *ListQueueItemsAction) GetTimestamp() float64 { - if l == nil { - return 0.0 - } - return l.Timestamp -} - -func (l *ListQueueItemsAction) GetReviewer() *string { - if l == nil { - return nil - } - return l.Reviewer -} - -// ListQueueItemsStatus - Status of the item -type ListQueueItemsStatus string - -const ( - ListQueueItemsStatusPending ListQueueItemsStatus = "pending" - ListQueueItemsStatusResolved ListQueueItemsStatus = "resolved" -) - -func (e ListQueueItemsStatus) ToPointer() *ListQueueItemsStatus { - return &e -} - -// IsExact returns true if the value matches a known enum value, false otherwise. -func (e *ListQueueItemsStatus) IsExact() bool { - if e != nil { - switch *e { - case "pending", "resolved": - return true - } - } - return false -} - -type ListQueueItemsData struct { - // Content ID - ID string `json:"id"` - // The content to be moderated - Content string `json:"content"` - // Content language - Language *string `json:"language,omitzero"` - Labels []ListQueueItemsLabel `json:"labels"` - // Conversation ID - ConversationID *string `json:"conversationId,omitzero"` - // Author ID - AuthorID *string `json:"authorId,omitzero"` - // Action IDs taken on this item - Actions []ListQueueItemsAction `json:"actions,omitzero"` - // Whether the item is flagged by any label - Flagged bool `json:"flagged"` - // Status of the item - Status ListQueueItemsStatus `json:"status"` - // Unix timestamp of when the item was created - Timestamp float64 `json:"timestamp"` - // Type of the content object - ContentType *string `json:"contentType,omitzero"` -} - -func (l ListQueueItemsData) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(l, "", false) -} - -func (l *ListQueueItemsData) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &l, "", false, nil); err != nil { - return err - } - return nil -} - -func (l *ListQueueItemsData) GetID() string { - if l == nil { - return "" - } - return l.ID -} - -func (l *ListQueueItemsData) GetContent() string { - if l == nil { - return "" - } - return l.Content -} - -func (l *ListQueueItemsData) GetLanguage() *string { - if l == nil { - return nil - } - return l.Language -} - -func (l *ListQueueItemsData) GetLabels() []ListQueueItemsLabel { - if l == nil { - return []ListQueueItemsLabel{} - } - return l.Labels -} - -func (l *ListQueueItemsData) GetConversationID() *string { - if l == nil { - return nil - } - return l.ConversationID -} - -func (l *ListQueueItemsData) GetAuthorID() *string { +func (l *ListQueueItemsRequest) GetAfterDate() *string { if l == nil { return nil } - return l.AuthorID + return l.AfterDate } -func (l *ListQueueItemsData) GetActions() []ListQueueItemsAction { +func (l *ListQueueItemsRequest) GetBeforeDate() *string { if l == nil { return nil } - return l.Actions -} - -func (l *ListQueueItemsData) GetFlagged() bool { - if l == nil { - return false - } - return l.Flagged -} - -func (l *ListQueueItemsData) GetStatus() ListQueueItemsStatus { - if l == nil { - return ListQueueItemsStatus("") - } - return l.Status -} - -func (l *ListQueueItemsData) GetTimestamp() float64 { - if l == nil { - return 0.0 - } - return l.Timestamp + return l.BeforeDate } -func (l *ListQueueItemsData) GetContentType() *string { +func (l *ListQueueItemsRequest) GetProjectID() *string { if l == nil { return nil } - return l.ContentType + return l.ProjectID } // ListQueueItemsResponseBody - Successful response type ListQueueItemsResponseBody struct { // The items on this page. - Data []ListQueueItemsData `json:"data"` + Data []components.QueueItem `json:"data"` // Where this page is in the full list. Pagination components.Pagination `json:"pagination"` } -func (l *ListQueueItemsResponseBody) GetData() []ListQueueItemsData { +func (l *ListQueueItemsResponseBody) GetData() []components.QueueItem { if l == nil { - return []ListQueueItemsData{} + return []components.QueueItem{} } return l.Data } diff --git a/internal/sdk/models/operations/listqueues.go b/internal/sdk/models/operations/listqueues.go new file mode 100644 index 0000000..70420e2 --- /dev/null +++ b/internal/sdk/models/operations/listqueues.go @@ -0,0 +1,102 @@ +// 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 ListQueuesRequest struct { + Page *int64 `default:"1" queryParam:"style=form,explode=true,name=page"` + PageSize *int64 `default:"20" queryParam:"style=form,explode=true,name=pageSize"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` +} + +func (l ListQueuesRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(l, "", false) +} + +func (l *ListQueuesRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &l, "", false, nil); err != nil { + return err + } + return nil +} + +func (l *ListQueuesRequest) GetPage() *int64 { + if l == nil { + return nil + } + return l.Page +} + +func (l *ListQueuesRequest) GetPageSize() *int64 { + if l == nil { + return nil + } + return l.PageSize +} + +func (l *ListQueuesRequest) GetProjectID() *string { + if l == nil { + return nil + } + return l.ProjectID +} + +// ListQueuesResponseBody - Successful response +type ListQueuesResponseBody struct { + // The items on this page. + Data []components.Queue `json:"data"` + // Where this page is in the full list. + Pagination components.Pagination `json:"pagination"` +} + +func (l *ListQueuesResponseBody) GetData() []components.Queue { + if l == nil { + return []components.Queue{} + } + return l.Data +} + +func (l *ListQueuesResponseBody) GetPagination() components.Pagination { + if l == nil { + return components.Pagination{} + } + return l.Pagination +} + +type ListQueuesResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *ListQueuesResponseBody + + Next func() (*ListQueuesResponse, error) +} + +func (l ListQueuesResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(l, "", false) +} + +func (l *ListQueuesResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &l, "", false, nil); err != nil { + return err + } + return nil +} + +func (l *ListQueuesResponse) GetHTTPMeta() components.HTTPMetadata { + if l == nil { + return components.HTTPMetadata{} + } + return l.HTTPMeta +} + +func (l *ListQueuesResponse) GetObject() *ListQueuesResponseBody { + if l == nil { + return nil + } + return l.Object +} diff --git a/internal/sdk/models/operations/resolvequeueitem.go b/internal/sdk/models/operations/resolvequeueitem.go index 71b73b3..a04ece6 100644 --- a/internal/sdk/models/operations/resolvequeueitem.go +++ b/internal/sdk/models/operations/resolvequeueitem.go @@ -7,26 +7,13 @@ import ( "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" ) -type ResolveQueueItemRequestBody struct { - // Optional comment - Comment *string `json:"comment,omitzero"` -} - -func (r *ResolveQueueItemRequestBody) GetComment() *string { - if r == nil { - return nil - } - return r.Comment -} - type ResolveQueueItemRequest struct { - // The queue ID + // The queue ID. ID string `pathParam:"style=simple,explode=false,name=id"` - // The item ID to resolve + // The item ID to resolve. ItemID string `pathParam:"style=simple,explode=false,name=itemId"` // 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. - ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` - Body ResolveQueueItemRequestBody `request:"mediaType=application/json"` + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` } func (r *ResolveQueueItemRequest) GetID() string { @@ -50,20 +37,11 @@ func (r *ResolveQueueItemRequest) GetProjectID() *string { return r.ProjectID } -func (r *ResolveQueueItemRequest) GetBody() ResolveQueueItemRequestBody { - if r == nil { - return ResolveQueueItemRequestBody{} - } - return r.Body -} - // ResolveQueueItemResponseBody - Successful response type ResolveQueueItemResponseBody struct { Success bool `json:"success"` - // Timestamp when the item was resolved + // When the item was resolved (ISO 8601). ResolvedAt string `json:"resolvedAt"` - // Optional comment - Comment *string `json:"comment,omitzero"` } func (r *ResolveQueueItemResponseBody) GetSuccess() bool { @@ -80,13 +58,6 @@ func (r *ResolveQueueItemResponseBody) GetResolvedAt() string { return r.ResolvedAt } -func (r *ResolveQueueItemResponseBody) GetComment() *string { - if r == nil { - return nil - } - return r.Comment -} - type ResolveQueueItemResponse struct { HTTPMeta components.HTTPMetadata `json:"-"` // Successful response diff --git a/internal/sdk/models/operations/resolvequeueitems.go b/internal/sdk/models/operations/resolvequeueitems.go new file mode 100644 index 0000000..c785a9e --- /dev/null +++ b/internal/sdk/models/operations/resolvequeueitems.go @@ -0,0 +1,102 @@ +// 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 ResolveQueueItemsRequestBody struct { + // The item IDs to resolve. Up to 100. + ItemIds []string `json:"itemIds"` +} + +func (r *ResolveQueueItemsRequestBody) GetItemIds() []string { + if r == nil { + return []string{} + } + return r.ItemIds +} + +type ResolveQueueItemsRequest struct { + // The queue ID. + ID string `pathParam:"style=simple,explode=false,name=id"` + // 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. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body ResolveQueueItemsRequestBody `request:"mediaType=application/json"` +} + +func (r *ResolveQueueItemsRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *ResolveQueueItemsRequest) GetProjectID() *string { + if r == nil { + return nil + } + return r.ProjectID +} + +func (r *ResolveQueueItemsRequest) GetBody() ResolveQueueItemsRequestBody { + if r == nil { + return ResolveQueueItemsRequestBody{} + } + return r.Body +} + +// ResolveQueueItemsResponseBody - Successful response +type ResolveQueueItemsResponseBody struct { + // Number of items resolved. + Resolved int64 `json:"resolved"` + // Item IDs that do not exist in the project. + NotFoundIds []string `json:"notFoundIds"` +} + +func (r *ResolveQueueItemsResponseBody) GetResolved() int64 { + if r == nil { + return 0 + } + return r.Resolved +} + +func (r *ResolveQueueItemsResponseBody) GetNotFoundIds() []string { + if r == nil { + return []string{} + } + return r.NotFoundIds +} + +type ResolveQueueItemsResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Successful response + Object *ResolveQueueItemsResponseBody +} + +func (r ResolveQueueItemsResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(r, "", false) +} + +func (r *ResolveQueueItemsResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &r, "", false, nil); err != nil { + return err + } + return nil +} + +func (r *ResolveQueueItemsResponse) GetHTTPMeta() components.HTTPMetadata { + if r == nil { + return components.HTTPMetadata{} + } + return r.HTTPMeta +} + +func (r *ResolveQueueItemsResponse) GetObject() *ResolveQueueItemsResponseBody { + if r == nil { + return nil + } + return r.Object +} diff --git a/internal/sdk/models/operations/submitcontent.go b/internal/sdk/models/operations/submitcontent.go index bae007b..7c5f540 100644 --- a/internal/sdk/models/operations/submitcontent.go +++ b/internal/sdk/models/operations/submitcontent.go @@ -4114,7 +4114,7 @@ func (t *TrustLevel) GetManual() bool { return t.Manual } -type Author struct { +type SubmitContentAuthor struct { // Author ID in Moderation API ID string `json:"id"` // The author's ID from your system @@ -4126,39 +4126,39 @@ type Author struct { TrustLevel TrustLevel `json:"trust_level"` } -func (a *Author) GetID() string { - if a == nil { +func (s *SubmitContentAuthor) GetID() string { + if s == nil { return "" } - return a.ID + return s.ID } -func (a *Author) GetExternalID() optionalnullable.OptionalNullable[string] { - if a == nil { +func (s *SubmitContentAuthor) GetExternalID() optionalnullable.OptionalNullable[string] { + if s == nil { return nil } - return a.ExternalID + return s.ExternalID } -func (a *Author) GetBlock() *Block { - if a == nil { +func (s *SubmitContentAuthor) GetBlock() *Block { + if s == nil { return nil } - return a.Block + return s.Block } -func (a *Author) GetStatus() AuthorStatus { - if a == nil { +func (s *SubmitContentAuthor) GetStatus() AuthorStatus { + if s == nil { return AuthorStatus("") } - return a.Status + return s.Status } -func (a *Author) GetTrustLevel() TrustLevel { - if a == nil { +func (s *SubmitContentAuthor) GetTrustLevel() TrustLevel { + if s == nil { return TrustLevel{} } - return a.TrustLevel + return s.TrustLevel } // Evaluation - The evaluation of the content after running the channel policies. @@ -4210,21 +4210,21 @@ func (e *Evaluation) GetShadowFlagged() *bool { return e.ShadowFlagged } -// SubmitContentRecommendationAction - The action to take based on the recommendation -type SubmitContentRecommendationAction string +// ActionResponse - The action to take based on the recommendation +type ActionResponse string const ( - SubmitContentRecommendationActionReview SubmitContentRecommendationAction = "review" - SubmitContentRecommendationActionAllow SubmitContentRecommendationAction = "allow" - SubmitContentRecommendationActionReject SubmitContentRecommendationAction = "reject" + ActionResponseReview ActionResponse = "review" + ActionResponseAllow ActionResponse = "allow" + ActionResponseReject ActionResponse = "reject" ) -func (e SubmitContentRecommendationAction) ToPointer() *SubmitContentRecommendationAction { +func (e ActionResponse) ToPointer() *ActionResponse { return &e } // IsExact returns true if the value matches a known enum value, false otherwise. -func (e *SubmitContentRecommendationAction) IsExact() bool { +func (e *ActionResponse) IsExact() bool { if e != nil { switch *e { case "review", "allow", "reject": @@ -4409,7 +4409,7 @@ func (m *MatchedRule) GetName() string { // Recommendation - The recommendation for the content based on the evaluation. type Recommendation struct { // The action to take based on the recommendation - Action SubmitContentRecommendationAction `json:"action"` + Action ActionResponse `json:"action"` // Reason codes for the recommendation. Standard codes plus a `rule:` code identifying the rule that produced the action. Can be used to display a reason to the user. ReasonCodes []ReasonCode `json:"reason_codes"` // Rules that matched during evaluation, if rules engine is active. @@ -4427,9 +4427,9 @@ func (r *Recommendation) UnmarshalJSON(data []byte) error { return nil } -func (r *Recommendation) GetAction() SubmitContentRecommendationAction { +func (r *Recommendation) GetAction() ActionResponse { if r == nil { - return SubmitContentRecommendationAction("") + return ActionResponse("") } return r.Action } @@ -4812,50 +4812,50 @@ func (e *EntityMatcherOutput) GetMatches() []Match { return e.Matches } -type SubmitContentLabel struct { +type Label struct { ID string `json:"id"` Probability float64 `json:"probability"` Flagged bool `json:"flagged"` ShadowFlagged *bool `json:"shadowFlagged,omitzero"` } -func (s SubmitContentLabel) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(s, "", false) +func (l Label) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(l, "", false) } -func (s *SubmitContentLabel) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &s, "", false, nil); err != nil { +func (l *Label) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &l, "", false, nil); err != nil { return err } return nil } -func (s *SubmitContentLabel) GetID() string { - if s == nil { +func (l *Label) GetID() string { + if l == nil { return "" } - return s.ID + return l.ID } -func (s *SubmitContentLabel) GetProbability() float64 { - if s == nil { +func (l *Label) GetProbability() float64 { + if l == nil { return 0.0 } - return s.Probability + return l.Probability } -func (s *SubmitContentLabel) GetFlagged() bool { - if s == nil { +func (l *Label) GetFlagged() bool { + if l == nil { return false } - return s.Flagged + return l.Flagged } -func (s *SubmitContentLabel) GetShadowFlagged() *bool { - if s == nil { +func (l *Label) GetShadowFlagged() *bool { + if l == nil { return nil } - return s.ShadowFlagged + return l.ShadowFlagged } // ClassifierOutput - Classifier policy. @@ -4867,8 +4867,8 @@ type ClassifierOutput struct { Probability float64 `json:"probability"` Flagged bool `json:"flagged"` // The keys of the flagged fields if submitting an object. - FlaggedFields []string `json:"flagged_fields,omitzero"` - Labels []SubmitContentLabel `json:"labels,omitzero"` + FlaggedFields []string `json:"flagged_fields,omitzero"` + Labels []Label `json:"labels,omitzero"` // Optional structured data produced by the policy. For face detection: { count, faces: [{ confidence, gender, age }] }. Data map[string]any `json:"data,omitzero"` } @@ -4916,7 +4916,7 @@ func (c *ClassifierOutput) GetFlaggedFields() []string { return c.FlaggedFields } -func (c *ClassifierOutput) GetLabels() []SubmitContentLabel { +func (c *ClassifierOutput) GetLabels() []Label { if c == nil { return nil } @@ -4930,59 +4930,59 @@ func (c *ClassifierOutput) GetData() map[string]any { return c.Data } -type PolicyResponseType string +type SubmitContentPolicyResponseType string const ( - PolicyResponseTypeClassifier PolicyResponseType = "classifier" - PolicyResponseTypeEntityMatcher PolicyResponseType = "entity_matcher" - PolicyResponseTypeUnknown PolicyResponseType = "UNKNOWN" + SubmitContentPolicyResponseTypeClassifier SubmitContentPolicyResponseType = "classifier" + SubmitContentPolicyResponseTypeEntityMatcher SubmitContentPolicyResponseType = "entity_matcher" + SubmitContentPolicyResponseTypeUnknown SubmitContentPolicyResponseType = "UNKNOWN" ) -// PolicyResponse - Policy output schema. -type PolicyResponse struct { +// SubmitContentPolicyResponse - Policy output schema. +type SubmitContentPolicyResponse struct { ClassifierOutput *ClassifierOutput `queryParam:"inline" union:"member"` EntityMatcherOutput *EntityMatcherOutput `queryParam:"inline" union:"member"` UnknownRaw json.RawMessage `json:"-" union:"unknown"` - Type PolicyResponseType + Type SubmitContentPolicyResponseType } -func CreatePolicyResponseClassifier(classifier ClassifierOutput) PolicyResponse { - typ := PolicyResponseTypeClassifier +func CreateSubmitContentPolicyResponseClassifier(classifier ClassifierOutput) SubmitContentPolicyResponse { + typ := SubmitContentPolicyResponseTypeClassifier - return PolicyResponse{ + return SubmitContentPolicyResponse{ ClassifierOutput: &classifier, Type: typ, } } -func CreatePolicyResponseEntityMatcher(entityMatcher EntityMatcherOutput) PolicyResponse { - typ := PolicyResponseTypeEntityMatcher +func CreateSubmitContentPolicyResponseEntityMatcher(entityMatcher EntityMatcherOutput) SubmitContentPolicyResponse { + typ := SubmitContentPolicyResponseTypeEntityMatcher - return PolicyResponse{ + return SubmitContentPolicyResponse{ EntityMatcherOutput: &entityMatcher, Type: typ, } } -func CreatePolicyResponseUnknown(raw json.RawMessage) PolicyResponse { - return PolicyResponse{ +func CreateSubmitContentPolicyResponseUnknown(raw json.RawMessage) SubmitContentPolicyResponse { + return SubmitContentPolicyResponse{ UnknownRaw: raw, - Type: PolicyResponseTypeUnknown, + Type: SubmitContentPolicyResponseTypeUnknown, } } -func (u PolicyResponse) GetUnknownRaw() json.RawMessage { +func (u SubmitContentPolicyResponse) GetUnknownRaw() json.RawMessage { return u.UnknownRaw } -func (u PolicyResponse) IsUnknown() bool { - return u.Type == PolicyResponseTypeUnknown +func (u SubmitContentPolicyResponse) IsUnknown() bool { + return u.Type == SubmitContentPolicyResponseTypeUnknown } -func (u *PolicyResponse) UnmarshalJSON(data []byte) (err error) { +func (u *SubmitContentPolicyResponse) UnmarshalJSON(data []byte) (err error) { previous := *u - *u = PolicyResponse{} + *u = SubmitContentPolicyResponse{} defer func() { if err != nil { *u = previous @@ -5000,12 +5000,12 @@ func (u *PolicyResponse) UnmarshalJSON(data []byte) (err error) { return fmt.Errorf("could not unmarshal discriminator: %w", err) } u.UnknownRaw = json.RawMessage(data) - u.Type = PolicyResponseTypeUnknown + u.Type = SubmitContentPolicyResponseTypeUnknown return nil } if dis == nil { u.UnknownRaw = json.RawMessage(data) - u.Type = PolicyResponseTypeUnknown + u.Type = SubmitContentPolicyResponseTypeUnknown return nil } @@ -5013,30 +5013,30 @@ func (u *PolicyResponse) UnmarshalJSON(data []byte) (err error) { case "classifier": classifierOutput := new(ClassifierOutput) if err := utils.UnmarshalJSON(data, &classifierOutput, "", true, nil); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Type == classifier) type ClassifierOutput within PolicyResponse: %w", string(data), err) + return fmt.Errorf("could not unmarshal `%s` into expected (Type == classifier) type ClassifierOutput within SubmitContentPolicyResponse: %w", string(data), err) } u.ClassifierOutput = classifierOutput - u.Type = PolicyResponseTypeClassifier + u.Type = SubmitContentPolicyResponseTypeClassifier return nil case "entity_matcher": entityMatcherOutput := new(EntityMatcherOutput) if err := utils.UnmarshalJSON(data, &entityMatcherOutput, "", true, nil); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Type == entity_matcher) type EntityMatcherOutput within PolicyResponse: %w", string(data), err) + return fmt.Errorf("could not unmarshal `%s` into expected (Type == entity_matcher) type EntityMatcherOutput within SubmitContentPolicyResponse: %w", string(data), err) } u.EntityMatcherOutput = entityMatcherOutput - u.Type = PolicyResponseTypeEntityMatcher + u.Type = SubmitContentPolicyResponseTypeEntityMatcher return nil default: u.UnknownRaw = json.RawMessage(data) - u.Type = PolicyResponseTypeUnknown + u.Type = SubmitContentPolicyResponseTypeUnknown return nil } } -func (u PolicyResponse) MarshalJSON() ([]byte, error) { +func (u SubmitContentPolicyResponse) MarshalJSON() ([]byte, error) { if u.ClassifierOutput != nil { return utils.MarshalJSON(u.ClassifierOutput, "", true) } @@ -5048,7 +5048,7 @@ func (u PolicyResponse) MarshalJSON() ([]byte, error) { if u.UnknownRaw != nil { return json.RawMessage(u.UnknownRaw), nil } - return nil, errors.New("could not marshal union type PolicyResponse: all fields are null") + return nil, errors.New("could not marshal union type SubmitContentPolicyResponse: all fields are null") } // CompassionInsight - Compassion insight. Identifies with or shows concern, empathy, or support for the feelings/emotions of others. @@ -5685,7 +5685,7 @@ type SubmitContentResponseBody struct { // Potentially modified content. Content ContentResponse `json:"content"` // The author of the content if your account has authors enabled. Requires you to send authorId when submitting content. - Author *Author `json:"author"` + Author *SubmitContentAuthor `json:"author"` // The evaluation of the content after running the channel policies. Evaluation Evaluation `json:"evaluation"` // The recommendation for the content based on the evaluation. @@ -5693,7 +5693,7 @@ type SubmitContentResponseBody struct { // What your casebook — the record of your past moderation decisions — found for this content, or null when it had nothing close enough to say, when the matching cases disagreed, or when casebook lookups are not switched on for this channel. Reports what the casebook found; whether it decided the outcome is shown in `recommendation`, where a higher-priority rule may have settled the item first. Casebook *Casebook `json:"casebook"` // Results of all policies in the channel. Sorted by highest probability. - Policies []PolicyResponse `json:"policies"` + Policies []SubmitContentPolicyResponse `json:"policies"` // Results of all insights enabled in the channel. Insights []Insight `json:"insights"` // Metadata about the moderation request @@ -5720,7 +5720,7 @@ func (s *SubmitContentResponseBody) GetContent() ContentResponse { return s.Content } -func (s *SubmitContentResponseBody) GetAuthor() *Author { +func (s *SubmitContentResponseBody) GetAuthor() *SubmitContentAuthor { if s == nil { return nil } @@ -5748,9 +5748,9 @@ func (s *SubmitContentResponseBody) GetCasebook() *Casebook { return s.Casebook } -func (s *SubmitContentResponseBody) GetPolicies() []PolicyResponse { +func (s *SubmitContentResponseBody) GetPolicies() []SubmitContentPolicyResponse { if s == nil { - return []PolicyResponse{} + return []SubmitContentPolicyResponse{} } return s.Policies } diff --git a/internal/sdk/models/operations/unresolvequeueitem.go b/internal/sdk/models/operations/unresolvequeueitem.go index 67676d9..8d74756 100644 --- a/internal/sdk/models/operations/unresolvequeueitem.go +++ b/internal/sdk/models/operations/unresolvequeueitem.go @@ -7,26 +7,13 @@ import ( "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" ) -type UnresolveQueueItemRequestBody struct { - // Optional reason for unresolving the item - Comment *string `json:"comment,omitzero"` -} - -func (u *UnresolveQueueItemRequestBody) GetComment() *string { - if u == nil { - return nil - } - return u.Comment -} - type UnresolveQueueItemRequest struct { - // The queue ID + // The queue ID. ID string `pathParam:"style=simple,explode=false,name=id"` - // The item ID to unresolve + // The item ID to unresolve. ItemID string `pathParam:"style=simple,explode=false,name=itemId"` // 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. - ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` - Body UnresolveQueueItemRequestBody `request:"mediaType=application/json"` + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` } func (u *UnresolveQueueItemRequest) GetID() string { @@ -50,19 +37,35 @@ func (u *UnresolveQueueItemRequest) GetProjectID() *string { return u.ProjectID } -func (u *UnresolveQueueItemRequest) GetBody() UnresolveQueueItemRequestBody { - if u == nil { - return UnresolveQueueItemRequestBody{} +// UnresolveQueueItemStatus - `pending` waits for review. `resolved` is reviewed. +type UnresolveQueueItemStatus string + +const ( + UnresolveQueueItemStatusPending UnresolveQueueItemStatus = "pending" + UnresolveQueueItemStatusResolved UnresolveQueueItemStatus = "resolved" +) + +func (e UnresolveQueueItemStatus) ToPointer() *UnresolveQueueItemStatus { + return &e +} + +// IsExact returns true if the value matches a known enum value, false otherwise. +func (e *UnresolveQueueItemStatus) IsExact() bool { + if e != nil { + switch *e { + case "pending", "resolved": + return true + } } - return u.Body + return false } // UnresolveQueueItemResponseBody - Successful response type UnresolveQueueItemResponseBody struct { Success bool `json:"success"` - // New status of the item - Status string `json:"status"` - // Timestamp when the item was unresolved + // `pending` waits for review. `resolved` is reviewed. + Status UnresolveQueueItemStatus `json:"status"` + // When the item was unresolved (ISO 8601). UnresolvedAt string `json:"unresolvedAt"` } @@ -73,9 +76,9 @@ func (u *UnresolveQueueItemResponseBody) GetSuccess() bool { return u.Success } -func (u *UnresolveQueueItemResponseBody) GetStatus() string { +func (u *UnresolveQueueItemResponseBody) GetStatus() UnresolveQueueItemStatus { if u == nil { - return "" + return UnresolveQueueItemStatus("") } return u.Status } diff --git a/internal/sdk/models/operations/updatewordlist.go b/internal/sdk/models/operations/updatewordlist.go index 64b3235..afc307c 100644 --- a/internal/sdk/models/operations/updatewordlist.go +++ b/internal/sdk/models/operations/updatewordlist.go @@ -14,7 +14,7 @@ type UpdateWordlistRequestBody struct { Key *string `json:"key,omitzero"` // New description for the wordlist Description *string `json:"description,omitzero"` - // New words for the wordlist. Replace the existing words with these new ones. Duplicate words will be ignored. + // New words for the wordlist. Replace the existing words with these new ones. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words. Words []string `json:"words,omitzero"` // Deprecated. Now using threshold in project settings. Strict *bool `json:"strict,omitzero"` diff --git a/internal/sdk/models/sdkerrors/notfounderror.go b/internal/sdk/models/sdkerrors/notfounderror.go index 0d42b9b..dd74cdf 100644 --- a/internal/sdk/models/sdkerrors/notfounderror.go +++ b/internal/sdk/models/sdkerrors/notfounderror.go @@ -17,6 +17,7 @@ type NotFoundError struct { // - `action_not_found`: The action does not exist. // - `author_not_found`: The author does not exist. // - `queue_not_found`: The review queue does not exist. + // - `queue_item_not_found`: The queue item does not exist in the project. // - `wordlist_not_found`: The wordlist does not exist. // - `webhook_not_found`: The webhook does not exist. // - `project_not_found`: The project does not exist. diff --git a/internal/sdk/moderationapi.go b/internal/sdk/moderationapi.go index 94082a5..d2a0f19 100644 --- a/internal/sdk/moderationapi.go +++ b/internal/sdk/moderationapi.go @@ -52,7 +52,9 @@ func Pointer[T any](v T) *T { return &v } // https://docs.moderationapi.com type ModerationAPI struct { SDKVersion string + Analytics *Analytics Authors *Authors + Feedback *Feedback Queue *Queue Actions *Actions Webhooks *Webhooks @@ -137,10 +139,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.4.0", + SDKVersion: "0.5.0", sdkConfiguration: config.SDKConfiguration{ - UserAgent: "speakeasy-sdk/go 0.4.0 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", - SDKVersion: "0.4.0", + UserAgent: "speakeasy-sdk/go 0.5.0 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", + SDKVersion: "0.5.0", GenVersion: "internal", OpenAPIDocVersion: "1.1.0", ServerList: ServerList, @@ -158,7 +160,9 @@ func New(opts ...SDKOption) *ModerationAPI { sdk.sdkConfiguration = sdk.hooks.SDKInit(sdk.sdkConfiguration) + sdk.Analytics = newAnalytics(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Authors = newAuthors(sdk, sdk.sdkConfiguration, sdk.hooks) + sdk.Feedback = newFeedback(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Queue = newQueue(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Actions = newActions(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Webhooks = newWebhooks(sdk, sdk.sdkConfiguration, sdk.hooks) diff --git a/internal/sdk/queue.go b/internal/sdk/queue.go index 1699190..d21735b 100644 --- a/internal/sdk/queue.go +++ b/internal/sdk/queue.go @@ -12,7 +12,9 @@ import ( "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" + "github.com/spyzhov/ajson" "net/http" + "net/url" ) type Queue struct { @@ -32,8 +34,384 @@ func newQueue(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks * } } +// List queues +// List the review queues of the project. The default queue comes first. The list is paginated. To get the item counts of a queue, use `GET /queue/{id}`. +func (s *Queue) List(ctx context.Context, request *operations.ListQueuesRequest, opts ...operations.Option) (*operations.ListQueuesResponse, 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 := url.JoinPath(baseURL, "/queue") + 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: "listQueues", + SecuritySource: s.sdkConfiguration.Security, + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + paginationCtx := ctx + + 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, "GET", opURL, nil) + 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) + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateQueryParams(ctx, req, request, nil, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + 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.ListQueuesResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + res.Next = func() (*operations.ListQueuesResponse, error) { + if request == nil { + request = &operations.ListQueuesRequest{} + } + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + b, err := ajson.Unmarshal(rawBody) + if err != nil { + return nil, err + } + var p int64 = 1 + if request.Page != nil { + p = *request.Page + } + nP := int64(p + 1) + nPs, err := ajson.Eval(b, "$.pagination.totalPages") + if err != nil { + return nil, err + } + if !nPs.IsNumeric() { + return nil, nil + } + + nPsVal, err := nPs.GetNumeric() + if err != nil { + return nil, err + } + // GetNumeric returns as float64 + if int(nPsVal) <= int(p) { + return nil, nil + } + r, err := ajson.Eval(b, "$.data") + if err != nil { + return nil, err + } + if !r.IsArray() { + return nil, nil + } + arr, err := r.GetArray() + if err != nil { + return nil, err + } + if len(arr) == 0 { + return nil, nil + } + + l := 0 + if request.PageSize != nil { + l = int(*request.PageSize) + } + if len(arr) < l { + return nil, nil + } + request.Page = &nP + + return s.List( + paginationCtx, + request, + opts..., + ) + } + + 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 operations.ListQueuesResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 500: + 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.InternalServerError + 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 + +} + // Retrieve - Get a queue -// Get a queue +// Get a review queue with its item counts. The counts use the filters and the date window of the queue, the same as the dashboard. func (s *Queue) Retrieve(ctx context.Context, request operations.GetQueueRequest, opts ...operations.Option) (*operations.GetQueueResponse, error) { o := operations.Options{} supportedOptions := []string{ @@ -340,7 +718,7 @@ func (s *Queue) Retrieve(ctx context.Context, request operations.GetQueueRequest } // GetStats - Get queue statistics -// Get detailed statistics about a moderation queue including review times, action counts, and trends +// Get review statistics of a queue for a period: resolved and pending items, the top reviewers and the resolved items per day. func (s *Queue) GetStats(ctx context.Context, request operations.GetQueueStatsRequest, opts ...operations.Option) (*operations.GetQueueStatsResponse, error) { o := operations.Options{} supportedOptions := []string{ diff --git a/internal/sdk/wordlist.go b/internal/sdk/wordlist.go index f4a97f8..79e264e 100644 --- a/internal/sdk/wordlist.go +++ b/internal/sdk/wordlist.go @@ -410,6 +410,245 @@ func (s *Wordlist) List(ctx context.Context, request *operations.ListWordlistsRe } +// Create wordlist +// Create a wordlist in the organization. You can add words now or later. Each word is embedded in the background for similarity matching. Use the embedding status endpoint to follow the progress. To use the wordlist in moderation, add it to a policy. +func (s *Wordlist) Create(ctx context.Context, request operations.CreateWordlistRequest, opts ...operations.Option) (*operations.CreateWordlistResponse, 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 := url.JoinPath(baseURL, "/wordlist") + 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: "createWordlist", + 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) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + 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.CreateWordlistResponse{ + 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 operations.CreateWordlistResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &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 == 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 + +} + // Retrieve - Get wordlist // Get a specific wordlist by ID func (s *Wordlist) Retrieve(ctx context.Context, request operations.GetWordlistRequest, opts ...operations.Option) (*operations.GetWordlistResponse, error) { @@ -718,7 +957,7 @@ func (s *Wordlist) Retrieve(ctx context.Context, request operations.GetWordlistR } // Update wordlist -// Update a wordlist +// Update a wordlist. Fields that you do not send stay the same. If you send words, they replace all existing words, and the embeddings are updated in the background. func (s *Wordlist) Update(ctx context.Context, request operations.UpdateWordlistRequest, opts ...operations.Option) (*operations.UpdateWordlistResponse, error) { o := operations.Options{} supportedOptions := []string{ diff --git a/internal/usage/bodyschemas.go b/internal/usage/bodyschemas.go index 4610907..dd72237 100644 --- a/internal/usage/bodyschemas.go +++ b/internal/usage/bodyschemas.go @@ -12,21 +12,22 @@ var bodySchemas = map[string]string{ "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\"}", + "createFeedback": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"What you tried to do, why, and what is missing or does not work. Plain text. Blank lines start new paragraphs.\",\"maxLength\":5000,\"minLength\":10,\"type\":\"string\"},\"source\":{\"default\":\"api\",\"description\":\"Where the feedback was written: MCP server, agent skill, CLI or API.\",\"enum\":[\"mcp\",\"skill\",\"cli\",\"api\"],\"type\":\"string\"},\"title\":{\"description\":\"A short summary of the request.\",\"maxLength\":120,\"minLength\":3,\"type\":\"string\"},\"type\":{\"default\":\"feature_request\",\"description\":\"The kind of feedback.\",\"enum\":[\"feature_request\",\"bug\",\"other\"],\"type\":\"string\"\x7d\x7d,\"required\":[\"title\",\"description\"],\"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\"}", + "createWordlist": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"Description of the wordlist\",\"type\":\"string\"},\"key\":{\"description\":\"Key of the wordlist. Default: made from the name. If the key is already in use, a suffix is added.\",\"type\":\"string\"},\"name\":{\"description\":\"Name of the wordlist\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"},\"words\":{\"description\":\"Words to add to the wordlist. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"required\":[\"name\"],\"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\"}", + "resolveQueueItems": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"itemIds\":{\"description\":\"The item IDs to resolve. Up to 100.\",\"items\":{\"type\":\"string\"},\"maxItems\":100,\"minItems\":1,\"type\":\"array\"\x7d\x7d,\"required\":[\"itemIds\"],\"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\"}", + "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. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words.\",\"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 644fdcc..87cd560 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.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"}}, + "": {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.5.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", "analytics", "auth", "authors", "channels", "completion", "configure", "content", "explore", "feedback", "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"}}, @@ -38,6 +38,13 @@ var usageNodes = map[string]usageNode{ "actions list": {head: "cmd \"list\" help=\"List moderation actions\"", flags: []usageItem{{key: "queue-id", text: " flag \"--queue-id \" help=\"string value\""}, {key: "page", text: " flag \"--page \" help=\"integer value\" default=1"}, {key: "page-size", text: " flag \"--page-size \" help=\"integer value\" default=20"}, {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: "all", text: " flag \"-a --all\" help=\"Automatically paginate and fetch all results (streams NDJSON for JSON output)\""}, {key: "max-pages", text: " flag \"--max-pages \" help=\"Maximum number of pages to fetch when using --all (0 = no limit)\" default=0"}}}, "actions retrieve": {head: "cmd \"retrieve\" help=\"Get an action\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The ID of the action to get. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The ID of the action to get. (or pass it as the [id] argument)\""}, {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 update": {head: "cmd \"update\" help=\"Update an action\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The ID of the action to update. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The ID of the action to update. (or pass it as the [id] argument)\""}, {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: "key", text: " flag \"-k --key \" help=\"User defined key of the action.\""}, {key: "name", text: " flag \"-n --name \" help=\"The name of the action.\""}, {key: "description", text: " flag \"--description \" help=\"The description of the action.\""}, {key: "type", text: " flag \"-t --type \" help=\"The type of the action. (options: AUTHOR_BLOCK, AUTHOR_BLOCK_TEMP, AUTHOR_UNBLOCK, AUTHOR_DELETE, AUTHOR_REPORT, AUTHOR_WARN, AUTHOR_CUSTOM, ITEM_REJECT, ITEM_ALLOW, ITEM_CUSTOM)\""}, {key: "built-in", text: " flag \"-b --built-in \" help=\"Whether the action is a built-in action or a custom one.\" default=#false"}, {key: "queue-behaviour", text: " flag \"--queue-behaviour \" help=\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. (options: REMOVE, ADD, NO_CHANGE)\" default=\"NO_CHANGE\""}, {key: "filter-in-queue-ids", text: " flag \"--filter-in-queue-ids \" help=\"The IDs of the queues the action is available in.\" var=#true"}, {key: "position", text: " flag \"--position \" help=\"Show the action in all queues, selected queues or no queues (to use via API only). (options: ALL_QUEUES, SOME_QUEUES, HIDDEN)\" default=\"ALL_QUEUES\""}, {key: "possible-values", text: " flag \"--possible-values \" help=\"The possible values of the action. The user will be prompted to select one of these values when executing the action. (JSON array)\""}, {key: "value-required", text: " flag \"-v --value-required\" help=\"Whether the action requires a value to be executed.\" default=#false"}, {key: "free-text", text: " flag \"--free-text\" help=\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\" default=#false"}, {key: "body", text: " flag \"--body \" help=\"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.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, + "analytics": {head: "cmd \"analytics\" help=\"Operations for analytics\"", children: []string{"authors", "automation", "channels", "policies", "summary", "volumes"}}, + "analytics authors": {head: "cmd \"authors\" help=\"Get the most flagged authors\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {key: "channel", text: " flag \"-c --channel \" help=\"string value\""}, {key: "limit", text: " flag \"-l --limit \" help=\"integer value\" default=6"}, {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.\""}}}, + "analytics automation": {head: "cmd \"automation\" help=\"Get the automation breakdown\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {key: "channel", text: " flag \"-c --channel \" help=\"string value\""}, {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.\""}}}, + "analytics channels": {head: "cmd \"channels\" help=\"Get analytics per channel\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {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.\""}}}, + "analytics policies": {head: "cmd \"policies\" help=\"Get the most flagged policies\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {key: "channel", text: " flag \"-c --channel \" help=\"string value\""}, {key: "limit", text: " flag \"-l --limit \" help=\"integer value\" default=8"}, {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.\""}}}, + "analytics summary": {head: "cmd \"summary\" help=\"Get the analytics summary\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {key: "channel", text: " flag \"-c --channel \" help=\"string value\""}, {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.\""}}}, + "analytics volumes": {head: "cmd \"volumes\" help=\"Get volumes over time\"", flags: []usageItem{{key: "range", text: " flag \"-r --range \" help=\"The time window. '24h' is the last 24 hours, in hourly buckets. '7d', '30d' and '90d' are whole UTC days that end with today, in daily buckets. (options: 24h, 7d, 30d, 90d)\" default=\"30d\""}, {key: "channel", text: " flag \"-c --channel \" help=\"string value\""}, {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.\""}}}, "auth": {head: "cmd \"auth\" help=\"Manage authentication credentials\"", children: []string{"login", "logout", "whoami"}}, "auth login": {head: "cmd \"login\" help=\"Interactively configure authentication credentials\"", flags: []usageItem{{key: "secret-key", text: " flag \"--secret-key \" help=\"HTTP Bearer\" env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\"", settings: true}}}, "auth logout": {head: "cmd \"logout\" help=\"Clear all stored authentication credentials\""}, @@ -45,7 +52,7 @@ var usageNodes = map[string]usageNode{ "authors": {head: "cmd \"authors\" help=\"Operations for authors\"", children: []string{"create", "delete", "list", "retrieve", "update"}}, "authors create": {head: "cmd \"create\" help=\"Create a new author\"", 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: "profile-picture", text: " flag \"--profile-picture \" help=\"URL of the author's profile picture\""}, {key: "external-link", text: " flag \"--external-link \" help=\"URL of the author's external profile\""}, {key: "name", text: " flag \"-n --name \" help=\"Author name or identifier\""}, {key: "email", text: " flag \"--email \" help=\"Author email address\""}, {key: "company", text: " flag \"-c --company \" help=\"The author's company or organization\""}, {key: "metadata", text: " flag \"--metadata \" help=\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\""}, {key: "first-seen", text: " flag \"-f --first-seen \" help=\"Timestamp when author first appeared\""}, {key: "last-seen", text: " flag \"-l --last-seen \" help=\"Timestamp of last activity\""}, {key: "manual-trust-level", text: " flag \"--manual-trust-level \" help=\"number value\""}, {key: "external-id", text: " flag \"--external-id \" help=\"External ID of the user, typically the ID of the author in your database. [required]\""}, {key: "body", text: " flag \"--body \" help=\"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.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "authors delete": {head: "cmd \"delete\" help=\"Delete an author\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {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.\""}}}, - "authors list": {head: "cmd \"list\" help=\"List authors\"", flags: []usageItem{{key: "page", text: " flag \"--page \" help=\"integer value\" default=1"}, {key: "page-size", text: " flag \"--page-size \" help=\"integer value\" default=20"}, {key: "sort-by", text: " flag \"--sort-by \" help=\"options: trustLevel, violationCount, reportCount, memberSince, lastActive, contentCount, flaggedContentRatio, averageSentiment\""}, {key: "sort-direction", text: " flag \"--sort-direction \" help=\"Sort direction (options: asc, desc)\" default=\"desc\""}, {key: "member-since-date", text: " flag \"-m --member-since-date \" help=\"string value\""}, {key: "last-active-date", text: " flag \"-l --last-active-date \" help=\"string value\""}, {key: "content-types", text: " flag \"-c --content-types \" help=\"list of values\" var=#true"}, {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: "all", text: " flag \"-a --all\" help=\"Automatically paginate and fetch all results (streams NDJSON for JSON output)\""}, {key: "max-pages", text: " flag \"--max-pages \" help=\"Maximum number of pages to fetch when using --all (0 = no limit)\" default=0"}}}, + "authors list": {head: "cmd \"list\" help=\"List authors\"", flags: []usageItem{{key: "page", text: " flag \"--page \" help=\"integer value\" default=1"}, {key: "page-size", text: " flag \"--page-size \" help=\"integer value\" default=20"}, {key: "sort-by", text: " flag \"--sort-by \" help=\"options: trustLevel, violationCount, memberSince, lastActive, contentCount, flaggedContentRatio, averageSentiment\""}, {key: "sort-direction", text: " flag \"--sort-direction \" help=\"Sort direction (options: asc, desc)\" default=\"desc\""}, {key: "member-since-date", text: " flag \"-m --member-since-date \" help=\"string value\""}, {key: "last-active-date", text: " flag \"-l --last-active-date \" help=\"string value\""}, {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: "all", text: " flag \"-a --all\" help=\"Automatically paginate and fetch all results (streams NDJSON for JSON output)\""}, {key: "max-pages", text: " flag \"--max-pages \" help=\"Maximum number of pages to fetch when using --all (0 = no limit)\" default=0"}}}, "authors retrieve": {head: "cmd \"retrieve\" help=\"Get author details\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {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.\""}}}, "authors update": {head: "cmd \"update\" help=\"Update author details\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {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: "profile-picture", text: " flag \"--profile-picture \" help=\"URL of the author's profile picture\""}, {key: "external-link", text: " flag \"--external-link \" help=\"URL of the author's external profile\""}, {key: "name", text: " flag \"-n --name \" help=\"Author name or identifier\""}, {key: "email", text: " flag \"--email \" help=\"Author email address\""}, {key: "company", text: " flag \"-c --company \" help=\"The author's company or organization\""}, {key: "metadata", text: " flag \"--metadata \" help=\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\""}, {key: "first-seen", text: " flag \"-f --first-seen \" help=\"Timestamp when author first appeared\""}, {key: "last-seen", text: " flag \"-l --last-seen \" help=\"Timestamp of last activity\""}, {key: "manual-trust-level", text: " flag \"--manual-trust-level \" help=\"number value\""}, {key: "body", text: " flag \"--body \" help=\"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.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "channels": {head: "cmd \"channels\" help=\"Operations for channels\"", children: []string{"create", "delete", "duplicate", "list", "retrieve", "update"}}, @@ -64,6 +71,8 @@ var usageNodes = map[string]usageNode{ "content": {head: "cmd \"content\" help=\"Operations for content\"", children: []string{"submit"}}, "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