PolicyAI v1 to v2 API Migration Guide
This guide helps you migrate from PolicyAI v1 API endpoints to the newer v2 API.
Why the change? v2 offers more flexibility:
- Simpler policy references: Use human-readable policy keys instead of UUIDs
- Richer output: Labels include more detailed output fields from the model
- Labeling Endpoints: Bundle one or more policies into a named, reusable endpoint — evaluate content against multiple policies in a single API call
- Multimodal Support: Moderate text and images together against your policies, with video and audio support coming soon!
- Policy Versioning/Drafting: Iterate on policy changes in draft mode, then finalize to create immutable, versioned releases with full change history
- Flexible Policy Text: Define custom prompt templates with content placeholders and configurable output fields
Key Concepts
What Changed
| v1 Concept | v2 Concept | Description |
|---|---|---|
| Tag | Labeling Endpoint | A named configuration that groups policies for evaluation |
| Decision | Label | The result of evaluating content against a policy |
| PolicyVersion | Policy + Version Number | Policies are now referenced by key and version number |
Terminology
| Term | Definition |
|---|---|
| Policy Key | A human-readable string identifier for a policy (e.g., content-moderation), replacing the UUIDs used in v1 |
| Labeler | Used to label content based on some policy or other criteria |
| Content Key | A named identifier on each content item (the key field) that maps it to an input defined in the policy. |
| Content Type | The media type of a content item: TEXT or IMAGE |
| Content Format | The encoding of the content data: PLAINTEXT for text, URL or BASE64 for images |
Migration Overview
Migrating to v2 is a two-step process:
-
Set up your v2 labeling endpoint — Create a labeling endpoint that references your policies. This replaces both v1 tags and direct policy version references.
-
Update your API integration — Modify your API calls to use the new v2 endpoint URL and request/response formats.
Which Migration Path Applies to You?
Depending on how you're currently using the v1 API, follow the appropriate path in Step 2:
| If you're using... | Follow... |
|---|---|
POST /policyai/v1/decisions/policies/{policyId}/versions/{policyVersionId} |
Migration Path 1: Single Policy Version Evaluation |
POST /policyai/v1/decisions/evaluate/{tag_name} |
Migration Path 2: Tag-Based Evaluation |
Both paths converge on the same v2 endpoint: /policyai/v2/labels/byEndpoint/{organizationId}/{endpointName}/
Note
You can label content directly against a policy without creating a Labeling Endpoint. However, we recommend using Labeling Endpoints, as they allow you evaluate content against multiple policies at once and swap policies in or out without changing your client code.
Step 1: Set Up Your v2 Labeling Endpoint
Before updating your API integration, you'll need to create a labeling endpoint in v2. This is required for both migration paths.
What is a Labeling Endpoint?
A labeling endpoint is a named, reusable configuration that defines:
- Which policies to run: Specified as a list of labelers
- Which versions to use: Each labeler specifies a policy key and version number
Endpoints serve the same purpose as v1 tags—grouping policies for evaluation—but with a simpler configuration model.
Creating an Endpoint
Endpoints can be created via the PolicyAI UI.
Navigate to the PolicyAI Manage Endpoints page. Click "Create New Endpoint," give your endpoint a descriptive name (e.g., dev, prod, staging), select the Policy Version you'd like to associate with the endpoint, and optionally add a description.
Note
The policies you've been using in v1 have already been ported to v2 by a member of the Musubi team. Simply create an endpoint associated with this policy, then you can use it to label content!
Best Practices
-
Use descriptive names: Choose names that reflect the environment or purpose (e.g.,
prod-moderation,staging-full-suite) -
Separate environments: Create different endpoints for development, staging, and production
-
Version control: When updating policies, test with a staging endpoint before updating production
Step 2: Update Your API Integration
Once your endpoint is set up, update your API calls. Choose the migration path that matches your current v1 integration.
Migration Path 1: Single Policy Version Evaluation
Migrating from: /policyai/v1/decisions/policies/{policyId}/versions/{policyVersionId}
Migrating to: /policyai/v2/labels/byEndpoint/{organizationId}/{endpointName}/
In v1, you could evaluate content against a specific policy version by providing the policy ID and version ID directly in the URL. In v2, you use the labeling endpoint you created in Step 1.
v1 Request
POST /policyai/v1/decisions/policies/{policyId}/versions/{policyVersionId}
Content-Type: application/json
Musubi-Api-Key: [your API key]
{
"content": [
{
"type": "TEXT",
"content": "Hello, I'm interested in your services. What are your rates?"
}
]
}
v1 Response
{
"messages": [],
"data": [
{
"content": [
{
"type": "TEXT",
"content": "Hello, I'm interested in your services. What are your rates?"
}
],
"createdTime": "2026-02-17T01:50:51.675552+00:00",
"createdByUserId": "01961bd5-85b0-7d6e-9baa-1f62bc4f2803",
"organizationId": "01975a9b-4b2c-7c76-bdce-3d38bf0b9725",
"id": "019c694b-0b1b-7c02-adf0-b4f1b81c1504",
"policyId": "019c6934-99ed-76a9-b4bd-c0194e292678",
"policyVersionId": "019c6934-99ef-7ba0-924c-c9ed5fa43141",
"policyVersionTagId": "019c693c-44c4-745f-a89a-f67d79598614",
"modelId": 5,
"assessment": "UNSAFE",
"severity": 2,
"category": "Selling",
"reason": "The user is asking about rates for a service, indicating selling.",
"extra": {
"resultType": "TEXT",
"promptFields": null
},
"messages": []
}
]
}
v2 Request
POST /policyai/v2/labels/byEndpoint/{organizationId}/{endpointName}/
Content-Type: application/json
Musubi-Api-Key: [your API key]
Example URL: /policyai/v2/labels/byEndpoint/your-organization-uuid/my-content-policy/
{
"content": [
{
"type": "TEXT",
"format": "PLAINTEXT",
"key": "content",
"data": "Hello, I'm interested in your services. What are your rates?"
}
]
}
| Field | Type | Description |
|---|---|---|
content |
array | List of content items to label |
content[].type |
string | Content type: "TEXT" or "IMAGE" |
content[].format |
string | Format: "PLAINTEXT" for text, "URL" or "BASE64" for images |
content[].key |
string | A key that matches the name of the input that's been configured in your policy |
content[].data |
string | The actual content (text string, URL, or base64-encoded image) |
v2 Response
{
"messages": [],
"data": {
"modifiedTime": "2026-02-17T01:31:51.613406+00:00",
"organizationId": "01975a9b-4b2c-7c76-bdce-3d38bf0b9725",
"createdTime": "2026-02-17T01:31:51.613406+00:00",
"id": "019c6939-a5bd-7103-850d-465ce0a25b90",
"content": [
{
"type": "TEXT",
"key": "content",
"format": "PLAINTEXT",
"data": "Hello, I'm interested in your services. What are your rates?",
"messages": [],
"sha256": "067c2c03ac4a76bef5b47c078404b1059d9d2ab75c2bc9d0ac7a6c60d03adad5",
"redacted": false
}
],
"labels": [
{
"extra": {},
"modifiedTime": "2026-02-17T01:31:51.613406+00:00",
"createdTime": "2026-02-17T01:31:51.613406+00:00",
"id": "019c6939-a5c2-7b68-83f7-7e7c46b83595",
"status": "COMPLETED",
"label": "Selling",
"assessment": "FLAGGED",
"labeler": {
"policyKey": "defaultpolicy_a09d9433",
"modelId": 100,
"modelParamValues": {},
"extraModelParamValues": {},
"type": "POLICY",
"versionNumber": 1
},
"labelerOutput": {
"label": "Selling"
},
"contentKeys": [
{
"type": "TEXT",
"key": "content"
}
]
}
],
"endpointId": "019c6938-41d8-7279-8fe2-8f89f457593b"
}
}
Note
The structure of the labelerOutput object depends on how your policy's custom outputs are defined - the only field guaranteed to be present is label.
Key Response Differences
| v1 Field | v2 Field | Notes |
|---|---|---|
assessment (SAFE/UNSAFE) |
assessment (CLEAR/FLAGGED) |
Renamed values |
severity (0-3) |
labelerOutput.severityScore (0-3) |
Severity is now part of the labeler output object, if configured |
category |
label |
The label returned by the policy |
reason |
labelerOutput.reason |
Reason is now part of the labeler output object, if configured |
policyVersionId |
labeler.policyKey + labeler.versionNumber |
Policy reference style changed |
Additionally, any custom output fields defined on your policy will be included in the labelerOutput response.
Migration Path 2: Tag-Based Evaluation
Migrating from: /policyai/v1/decisions/evaluate/{tag_name}
Migrating to: /policyai/v2/labels/byEndpoint/{organizationId}/{endpointName}/
Understanding v1 Tags vs v2 Labeling Endpoints
In v1, tags were used to group multiple policy versions together for batch evaluation. You would:
- Create a tag in the UI
- Associate multiple policy versions with that tag
- Call the tag endpoint to evaluate content against all associated policies
In v2, labeling endpoints serve the same purpose:
- Create a Labeling Endpoint via the UI (see Step 1 above)
- Configure the list of labelers (policies + versions) directly in the endpoint
- Call the endpoint to label content against all configured policies
| Aspect | v1 Tags | v2 Endpoints |
|---|---|---|
| Configuration | Associate existing policy versions | Define labelers with policy key + version |
| URL Structure | /evaluate/{tag_name} |
/byEndpoint/{orgId}/{endpointName}/ |
| Response | Array of decisions | Label group with array of labels |
v1 Request
POST /policyai/v1/decisions/evaluate/{tag_name}
Content-Type: application/json
Musubi-Api-Key: [your API key]
Example URL: /policyai/v1/decisions/evaluate/production
{
"content": [
{
"type": "TEXT",
"content": "Check out my profile for special offers!"
}
]
}
v1 Response
{
"messages": [],
"data": [
{
"content": [
{
"type": "TEXT",
"content": "Check out my profile for special offers!"
}
],
"createdTime": "2026-02-17T01:36:30.433656+00:00",
"createdByUserId": "01961bd5-85b0-7d6e-9baa-1f62bc4f2803",
"organizationId": "01975a9b-4b2c-7c76-bdce-3d38bf0b9725",
"id": "019c693d-e6e0-7b3f-999d-1099cfeba876",
"policyId": "019c6934-99ed-76a9-b4bd-c0194e292678",
"policyVersionId": "019c6934-99ef-7ba0-924c-c9ed5fa43141",
"policyVersionTagId": "019c693c-44c4-745f-a89a-f67d79598614",
"modelId": 5,
"assessment": "UNSAFE",
"severity": 1,
"category": "Selling",
"reason": "The content promotes special offers, which falls under selling.",
"extra": {
"resultType": "TEXT",
"promptFields": null
},
"messages": []
}
]
}
v2 Request
POST /policyai/v2/labels/byEndpoint/{organizationId}/{endpointName}/
Content-Type: application/json
Musubi-Api-Key: [your API key]
Example URL: /policyai/v2/labels/byEndpoint/your-organization-uuid/production/
{
"content": [
{
"type": "TEXT",
"format": "PLAINTEXT",
"key": "message",
"data": "Check out my profile for special offers!"
}
]
}
v2 Response
{
"messages": [],
"data": {
"modifiedTime": "2026-02-17T01:38:57.997848+00:00",
"organizationId": "01975a9b-4b2c-7c76-bdce-3d38bf0b9725",
"createdTime": "2026-02-17T01:38:57.997848+00:00",
"id": "019c6940-274d-7ca6-b5be-42607a141cfc",
"content": [
{
"type": "TEXT",
"key": "content",
"format": "PLAINTEXT",
"data": "Check out my profile for special offers!",
"messages": [],
"sha256": "31319ddf797178fd79529416405d0380e4be93c60239816695d7be072df0c8a3",
"redacted": false
}
],
"labels": [
{
"extra": {},
"modifiedTime": "2026-02-17T01:38:57.997848+00:00",
"createdTime": "2026-02-17T01:38:57.997848+00:00",
"id": "019c6940-2751-75d9-b305-62f48f8398e5",
"status": "COMPLETED",
"label": "Selling",
"assessment": "FLAGGED",
"labeler": {
"policyKey": "defaultpolicy_a09d9433",
"modelId": 100,
"modelParamValues": {},
"extraModelParamValues": {},
"type": "POLICY",
"versionNumber": 1
},
"labelerOutput": {
"label": "Selling"
},
"contentKeys": [
{
"type": "TEXT",
"key": "content"
}
]
}
],
"endpointId": "019c6938-41d8-7279-8fe2-8f89f457593b"
}
}
Reference: Content Format Changes
v1 Content Format
{
"content": [
{
"type": "TEXT",
"content": "Your text here"
}
]
}
v2 Content Format
{
"content": [
{
"type": "TEXT",
"format": "PLAINTEXT",
"key": "content",
"data": "Your text here"
}
]
}
Content Types Reference
| Content Type | Format | Example |
|---|---|---|
| Text | type: "TEXT", format: "PLAINTEXT" |
"data": "Hello world" |
| Image URL | type: "IMAGE", format: "URL" |
"data": "https://example.com/image.jpg" |
| Image Base64 | type: "IMAGE", format: "BASE64" |
"data": "iVBORw0KGgo..." |
Content Keys
The key field in v2 content maps to the content keys defined in your policies.
Reference: Assessment Value Changes
| v1 Assessment | v2 Assessment | Meaning |
|---|---|---|
SAFE |
CLEAR |
No policy violation detected |
UNSAFE |
FLAGGED |
Policy violation detected |
Migration Checklist
- Identify all v1 API calls in your codebase
- Create v2 endpoint(s) corresponding to your v1 tags/policies (see Step 1)
- Update content payload format (add
format,key, renamecontenttodata) - Update response parsing to handle new structure (
labelsarray,labelerOutput) - Update assessment value checks (
SAFE→CLEAR,UNSAFE→FLAGGED) - Test thoroughly in staging before switching production traffic
Need Help?
If you have further questions about migrating to the v2 API, please contact a member of our team or refer to the API documentation.