Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Migrating from 4.x to 5.x

Version 5 is generated from the OpenAPI spec with a new generator. Most calls keep their names. Check each item below before you upgrade.

```bash
npm install @moderation-api/sdk@5
```

## Client

- The client class is `ModerationApi` (was `ModerationAPI`). It is also the default export, so `import ModerationApi from "@moderation-api/sdk"` keeps working.
- The client reads the API key from `MODAPI_SECRET_KEY`. To set it in code, pass `secretKey`.

## Imports

The `@moderation-api/sdk/resources` import path is removed. Import types from:

| Types | Import path |
| --- | --- |
| Requests and responses | `@moderation-api/sdk/models/operations` |
| Shared types, for example `PublicAuthor` | `@moderation-api/sdk/models` |
| Error classes | `@moderation-api/sdk/models/errors` |
| Webhook events, `validateWebhook` | `@moderation-api/sdk` |

## Renamed and removed methods

| 4.x | 5.x |
| --- | --- |
| `account.list()` | `account.retrieve()` |
| `actions.execute.execute(params)` | `actions.execute(params)` |
| `actions.execute.executeByID(id, params)` | `actions.execute({ actionId, ... })` or `actions.execute({ actionKey, ... })` |
| `webhooks.constructEvent(rawBody, signature, secret)` | `await validateWebhook({ request, secret })` |
| `auth.*` | Removed |
| `content.stream` | Removed. See [Real-time voice](https://docs.moderationapi.com/content-moderation/real-time-voice). |

## Webhooks

`webhooks.constructEvent` was synchronous and took the raw body and the signature. `validateWebhook` is async, is exported from the package root and takes the whole request: a Fetch `Request`, or a Node.js request (Express, Next.js Pages Router). It reads the secret from `MODAPI_WEBHOOK_SECRET` when you do not pass `secret`.

```typescript
// 4.x
const event = client.webhooks.constructEvent(rawBody, signature, secret);

// 5.x
import { validateWebhook } from "@moderation-api/sdk";
const event = await validateWebhook({ request: req });
```

Webhook payloads keep the API's snake_case field names (`flagged_fields`, `external_id`). Only the client's requests and responses use camelCase.

## camelCase fields

Request and response fields are camelCase. The 4.x types used the API's snake_case names. For example, `authors.create` and `authors.update`:

| 4.x | 5.x |
| --- | --- |
| `external_id` | `externalId` |
| `external_link` | `externalLink` |
| `profile_picture` | `profilePicture` |
| `first_seen` | `firstSeen` |
| `last_seen` | `lastSeen` |
| `manual_trust_level` | `manualTrustLevel` |
| `metadata.email_verified` | `metadata.emailVerified` |
| `metadata.phone_verified` | `metadata.phoneVerified` |
| `metadata.identity_verified` | `metadata.identityVerified` |
| `metadata.is_paying_customer` | `metadata.isPayingCustomer` |

The same applies to responses, for example `author.trustLevel`, `result.evaluation.flagProbability` and `result.recommendation.reasonCodes`.

## Lists

List methods take `page` and `pageSize`, and return `{ data, pagination }` instead of a plain array or a resource-specific shape. You can also iterate over the result to get every page.

## Errors

There is one error class per HTTP status (`BadRequestError`, `NotFoundError`, `RateLimitError`, ...). `code` is the specific error code (for example `invalid_api_key`), not the HTTP category. The old `data` field is removed.

## Organization keys

Organization keys work only with `projects` and `channels`. Project keys work with everything else. Use one client per key.

## Renamed types

| 4.x (`@moderation-api/sdk/resources`) | 5.x |
| --- | --- |
| `AccountListResponse` | `GetAccountResponse` |
| `ActionCreateParams` | `CreateActionRequestBody` |
| `ActionCreateResponse` | `CreateActionResponse` |
| `ActionRetrieveResponse` | `GetActionResponse` |
| `ActionUpdateParams` | `UpdateActionRequestBody` |
| `ActionUpdateResponse` | `UpdateActionResponse` |
| `ActionListParams` | `ListActionsRequest` |
| `ActionListResponse` | `ListActionsResponse` |
| `ActionDeleteResponse` | `DeleteActionResponse` |
| `ExecuteExecuteParams` | `ExecuteActionRequestBody` |
| `ExecuteExecuteResponse` | `ExecuteActionResponse` |
| `AuthorCreateParams` | `CreateAuthorRequestBody` |
| `AuthorUpdateParams` | `UpdateAuthorRequestBody` |
| `AuthorListParams` | `ListAuthorsRequest` |
| `AuthorCreateResponse`, `AuthorRetrieveResponse`, `AuthorUpdateResponse` | `PublicAuthor` (`@moderation-api/sdk/models`) |
| `AuthorListResponse` | `ListAuthorsResponse` |
| `AuthorDeleteResponse` | `DeleteAuthorResponse` |
| `ContentSubmitParams` | `SubmitContentRequestBody` |
| `ContentSubmitResponse` | `SubmitContentResponse` |
| `ItemListParams` | `ListQueueItemsRequest` |
| `ItemListResponse` | `ListQueueItemsResponse` |
| `ItemResolveParams` | `ResolveQueueItemRequestBody` |
| `ItemResolveResponse` | `ResolveQueueItemResponse` |
| `ItemUnresolveParams` | `UnresolveQueueItemRequestBody` |
| `ItemUnresolveResponse` | `UnresolveQueueItemResponse` |
| `QueueRetrieveResponse` | `GetQueueResponse` |
| `QueueGetStatsParams` | `GetQueueStatsRequest` |
| `QueueGetStatsResponse` | `GetQueueStatsResponse` |
| `WebhookEvent` | `WebhookEvent` (`@moderation-api/sdk`) |
| `WebhookSecretRetrieveResponse` | `GetWebhookSecretResponse` |
| `WebhookCreateParams` | `CreateWebhookRequestBody` |
| `WebhookCreateResponse` | `CreateWebhookResponse` |
| `WebhookRetrieveResponse` | `GetWebhookResponse` |
| `WebhookUpdateParams` | `UpdateWebhookRequestBody` |
| `WebhookUpdateResponse` | `UpdateWebhookResponse` |
| `WebhookListResponse` | `ListWebhooksResponse` |
| `WebhookDeleteResponse` | `DeleteWebhookResponse` |
| `WordlistRetrieveResponse` | `GetWordlistResponse` |
| `WordlistUpdateParams` | `UpdateWordlistRequestBody` |
| `WordlistUpdateResponse` | `UpdateWordlistResponse` |
| `WordlistListResponse` | `ListWordlistsResponse` |
| `WordlistGetEmbeddingStatusResponse` | `GetWordlistEmbeddingStatusResponse` |
| `WordAddParams` | `AddWordlistWordsRequestBody` |
| `WordAddResponse` | `AddWordlistWordsResponse` |
| `WordRemoveParams` | `RemoveWordlistWordsRequest` |
| `WordRemoveResponse` | `RemoveWordlistWordsResponse` |
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,28 @@ switch (result.recommendation.action) {
case "reject":
// Block the content
break;
case "review":
// Publish or hold it, and send it to a review queue
break;
case "allow":
// Publish the content
break;
case "review":
default:
// Publish or hold it, and send it to a review queue.
// `default` also catches actions added to the API later.
break;
}

for (const policy of result.policies) {
console.log(`${policy.id}: flagged=${policy.flagged}`);
}

// For object content: the fields that any flagging policy flagged
console.log(result.evaluation.flaggedFields);
```

### Values added later

Some fields are open enums, for example `recommendation.action` and the error `code`. The API can add a value, and an older SDK version then returns it as a plain string. It does not throw. Always handle unknown values: for `recommendation.action`, treat an unknown value as `review`, as the `default` branch above does.

## Verifying webhooks

Moderation API signs every webhook delivery with your project's webhook signing secret. The signature is in the `modapi-signature` header.
Expand Down Expand Up @@ -266,6 +275,36 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
```

### Handle an event

`validateWebhook` returns a `WebhookEvent`, a union of the event types. Switch on `event.type` and TypeScript knows the shape of `event.data.object`. Each event also has its own type, for example `QueueItemRejectedEvent` or `AuthorBlockedEvent`.

Webhook payloads use the API's field names, in snake_case (`flagged_fields`, `external_id`). The request and response objects of the client methods use camelCase (`flaggedFields`, `externalId`).

The payload is not validated against a schema, so a field or an event type added to the API later never fails a delivery. An event type that this SDK version does not know reaches the `default` branch. Read it as `UnknownWebhookEvent`.

```typescript
import { validateWebhook, type UnknownWebhookEvent } from "@moderation-api/sdk";

const event = await validateWebhook({ request });

switch (event.type) {
case "queue_item.rejected": {
const { item, author } = event.data.object;
console.log(item?.id, item?.flagged_fields, author?.external_id);
break;
}
case "author.blocked": {
console.log(event.data.object.author.external_id);
break;
}
default: {
const unknown = event as UnknownWebhookEvent;
console.log(`Unhandled event ${unknown.type}`);
}
}
```
<!-- End Custom [guide] -->


Expand Down Expand Up @@ -776,13 +815,16 @@ import ModerationApi from "@moderation-api/sdk";
import { ModerationApi } from "@moderation-api/sdk";
```

Request and response types are in `@moderation-api/sdk/models/operations`, shared types in `@moderation-api/sdk/models`, error classes in `@moderation-api/sdk/models/errors` and webhook events in `@moderation-api/sdk/models/webhooks`:
Request and response types are in `@moderation-api/sdk/models/operations`, shared types in `@moderation-api/sdk/models`, error classes in `@moderation-api/sdk/models/errors`. Webhook event types (`WebhookEvent`, `QueueItemRejectedEvent`, ...) are exported from the package root, next to `validateWebhook`:

```typescript
import type { SubmitContentRequest, SubmitContentResponse } from "@moderation-api/sdk/models/operations";
import { PermissionDeniedError } from "@moderation-api/sdk/models/errors";
import type { WebhookEvent } from "@moderation-api/sdk";
```

Upgrading from 4.x? Read [MIGRATION.md](MIGRATION.md).

## Timeouts

Set a timeout in milliseconds for all requests on the client, or for one request:
Expand Down
Loading
Loading