Skip to main content

Preview the cost before submitting

Creating a submission consumes credits. To see the cost first, call POST /api/integrations/pricing/estimate with the same blobPaths you intend to submit — nothing else is needed. Do this after uploading your assets and before creating the submission — it is read-only and consumes nothing. The asset type and MIME type are derived from the blobPath server-side (the same way the submission does), and the price is computed from a metric measured on the uploaded bytes (video duration, document page count) — so the estimate matches what you are charged when you submit. The measurement is cached, so the submission that follows does not re-read the file.
import requests

API_KEY = "your-api-key-here"
API_BASE = "https://mm-midmarket-integrations-api-preview.azurewebsites.net"

# blobPaths returned by your earlier uploads
estimate = requests.post(
    f"{API_BASE}/api/integrations/pricing/estimate",
    headers={"X-API-Key": API_KEY},
    json={
        "files": [
            {"blobPath": "<asset-id>/campaign-video.mp4"},
            {"blobPath": "<asset-id>/brief.pdf"},
        ]
    },
).json()

print(f"This submission will cost {estimate['totalCredits']} credits")
for f in estimate["files"]:
    print(f"  {f['blobPath']}: {f['credits']} credits (priceable={f['priceable']})")
Each result echoes its blobPath so you can map it back to the file you sent. The response returns the cost per file plus a totalCredits for the batch:
{
  "files": [
    { "blobPath": "<asset-id>/campaign-video.mp4", "assetType": "video",
      "metric": "durationSeconds", "metricValue": 42, "credits": 3, "priceable": true },
    { "blobPath": "<asset-id>/brief.pdf", "assetType": "document",
      "metric": "pages", "metricValue": 8, "credits": 1, "priceable": true }
  ],
  "totalCredits": 4
}
A file with priceable: false could not be measured (for example an unreadable PDF). It costs 0 here and is excluded from totalCredits, but it will be rejected when you create the submission — re-upload it as a readable file first.

Creating a submission

A submission is a request to process one or more uploaded assets. After creating a submission, the API queues your assets for processing. Each asset object carries blobPath (the path returned by an upload) and, optionally, previousSubmissionId for versioning and a per-asset sidekickIds list (see Choosing sidekicks below). Do not send filename or contentType in asset objects — they are not part of the schema. The other top-level fields are sidekickIds (required — see below), name (an optional display name for the submission, up to 120 characters — defaults to the first asset’s filename if omitted), and metadata (an optional arbitrary object).

Choosing sidekicks

sidekickIds is the submission-level list of sidekicks to run for compliance checking. At least one is required — a submission with no sidekick has nothing to check, so a request that omits sidekickIds or sends an empty list is rejected with 422. To discover which sidekicks your workspace can run — and the asset types each one accepts — call List available sidekicks and use the returned id values. An asset whose type no selected sidekick supports (e.g. a PDF paired only with a video sidekick) is rejected at submit time, so make sure each asset is covered by a sidekick that handles its type. Per-asset selection (optional). Each asset may also carry its own sidekickIds to run only a subset of the sidekicks on that asset. A per-asset list must be a subset of the submission-level sidekickIds; an empty (or omitted) per-asset list means the asset inherits the full submission-level set. Use this when different assets in one submission need different checks — keep the submission-level list as the union of every asset’s selection.
Breaking change (June 2026). sidekickIds was previously optional and accepted an empty list. It is now required with at least one entry. Clients that currently send "sidekickIds": [] must be updated to pass one or more sidekick IDs (fetch them from List available sidekicks) before upgrading.
Testing with a sandbox (sk_test_) key? The asset filename must contain a scenario marker so the sandbox knows which outcome to simulate. See the Sandbox environment guide.

Single asset submission

curl -X POST https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring product launch",
    "assets": [
      { "blobPath": "<workspace-id>/recording.mp4" }
    ],
    "sidekickIds": ["<sidekick-id>"],
    "metadata": { "campaign": "spring-2026" }
  }'

Multiple asset submission

Submit multiple assets in a single request for batch processing:
{
  "assets": [
    { "blobPath": "<workspace-id>/video1.mp4" },
    { "blobPath": "<workspace-id>/video2.mp4" },
    { "blobPath": "<workspace-id>/contract.pdf", "sidekickIds": ["<document-sidekick-id>"] }
  ],
  "sidekickIds": ["<video-sidekick-id>", "<document-sidekick-id>"],
  "metadata": { "campaign": "spring-2026" }
}
The two videos inherit the full submission-level set, while contract.pdf narrows its checks to just the document sidekick via its own sidekickIds. The per-asset list stays a subset of the submission-level sidekickIds.
Each asset is processed independently. Batch related assets into one submission to reduce request volume.

Attaching tags and supporting context

A submission can also carry top-level tags and contextItems (both optional). Tags are key/value labels (found-or-created automatically — no pre-creation or UUID lookup needed). Context items are supporting files or links the review can use. Each entry’s applyTo is "all" (default) or a list of asset blobPaths. A context file is uploaded the same way as an asset — request an upload URL, PUT the bytes, then reference the returned blobPath:
import requests

API_KEY = "your-api-key-here"
API_BASE = "https://mm-midmarket-integrations-api-preview.azurewebsites.net"

def upload(filename: str, content_type: str, path: str) -> str:
    data = requests.post(
        f"{API_BASE}/api/integrations/uploads/url",
        headers={"X-API-Key": API_KEY},
        json={"filename": filename, "contentType": content_type},
    ).json()
    with open(path, "rb") as f:
        requests.put(
            data["uploadUrl"], headers={"x-ms-blob-type": "BlockBlob"}, data=f
        ).raise_for_status()
    return data["blobPath"]

asset_blob = upload("recording.mp4", "video/mp4", "recording.mp4")
# Context files allow document types (PDF, DOCX, TXT) as well as media.
evidence_blob = upload("evidence.pdf", "application/pdf", "evidence.pdf")

submission = requests.post(
    f"{API_BASE}/api/integrations/submissions",
    headers={"X-API-Key": API_KEY},
    json={
        "assets": [{"blobPath": asset_blob}],
        "tags": [{"key": "Campaign", "value": "Summer", "applyTo": "all"}],
        "contextItems": [
            {
                "kind": "file",
                "contextType": "substantiation",
                "blobPath": evidence_blob,
                "name": "evidence.pdf",
                "mimeType": "application/pdf",
                "applyTo": "all",
            },
            {
                "kind": "link",
                "contextType": "legal",
                "url": "https://example.com/brand-guidelines",
                "linkName": "Brand guidelines",
                "applyTo": "all",
            },
        ],
    },
).json()
print(submission["submissionId"])
See Tags and supporting context for the full field reference and the allowed contextType values.

Response format

A successful create returns 201 with this shape:
{
  "submissionId": "550e8400-e29b-41d4-a716-446655440000",
  "workflowId": "asset-processing-...",
  "ablyChannel": "submission:550e8400-...",
  "status": "submitted",
  "assets": [
    {
      "assetId": "a1b2...",
      "versionGroupId": "g1...",
      "versionNumber": 1
    }
  ]
}
Read the identifier from submissionId (not id). You’ll use it to check status and retrieve results.

Asset versioning

Versioning is explicit. To create a new version of an asset, submit a new submission whose asset references the prior asset through previousSubmissionId. The new submission becomes the next version in the same version group.
1

Upload the revised file

Upload the corrected file and get its blobPath.
2

Reference the previous submission

Include previousSubmissionId on the asset so the API links it as the next version.
3

List versions

Use /submissions/{submission_id}/versions to list all versions in the group.
Example: resubmitting after fixing audio issues.
import requests

API_BASE = "https://mm-midmarket-integrations-api-preview.azurewebsites.net"

# The original submission whose asset you want to supersede
previous_submission_id = "550e8400-e29b-41d4-a716-446655440000"

response = requests.post(
    f"{API_BASE}/api/integrations/submissions",
    headers={"X-API-Key": "your-api-key-here"},
    json={
        "assets": [
            {
                "blobPath": "<workspace-id>/recording-fixed.mp4",
                "previousSubmissionId": previous_submission_id,
            }
        ],
        "sidekickIds": ["<sidekick-id>"],
    },
)

new_submission = response.json()
print(f"New version submission: {new_submission['submissionId']}")
print(f"Version number: {new_submission['assets'][0]['versionNumber']}")
List all versions in the group:
curl -H "X-API-Key: your-api-key-here" \
  https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions/550e8400-e29b-41d4-a716-446655440000/versions
Response:
{
  "versions": [
    {
      "assetId": "a1...",
      "submissionId": "550e8400-...",
      "versionNumber": 1,
      "name": "recording.mp4",
      "createdAt": "2026-06-03T12:00:00Z",
      "status": "complete",
      "issueCount": 3
    },
    {
      "assetId": "a2...",
      "submissionId": "660e8400-...",
      "versionNumber": 2,
      "name": "recording-fixed.mp4",
      "createdAt": "2026-06-04T10:00:00Z",
      "status": "complete",
      "issueCount": 1
    }
  ],
  "total": 2
}

Batch submission example

A complete example of uploading multiple files and creating a batch submission:
import requests
from pathlib import Path

API_KEY = "your-api-key-here"
API_BASE = "https://mm-midmarket-integrations-api-preview.azurewebsites.net"

def batch_submit_files(file_paths: list[str]) -> str:
    """Upload files via presigned URLs and create one batch submission."""

    assets = []
    for file_path in file_paths:
        file = Path(file_path)

        # Get a presigned URL
        url_response = requests.post(
            f"{API_BASE}/api/integrations/uploads/url",
            headers={"X-API-Key": API_KEY},
            json={
                "filename": file.name,
                "contentType": get_content_type(file.suffix),
            },
        )
        url_response.raise_for_status()
        data = url_response.json()

        # Upload the file to Azure
        with open(file_path, "rb") as f:
            requests.put(
                data["uploadUrl"],
                headers={"x-ms-blob-type": "BlockBlob"},
                data=f,
            ).raise_for_status()

        assets.append({"blobPath": data["blobPath"]})
        print(f"Uploaded {file.name}")

    # Create one submission with all assets
    response = requests.post(
        f"{API_BASE}/api/integrations/submissions",
        headers={"X-API-Key": API_KEY},
        json={"assets": assets, "sidekickIds": ["<sidekick-id>"]},
    )
    response.raise_for_status()

    submission = response.json()
    print(f"Submission created: {submission['submissionId']}")
    return submission["submissionId"]

def get_content_type(suffix: str) -> str:
    types = {
        ".mp4": "video/mp4",
        ".mp3": "audio/mpeg",
        ".pdf": "application/pdf",
        ".txt": "text/plain",
    }
    return types.get(suffix.lower(), "application/octet-stream")

# Usage
files = ["video1.mp4", "video2.mp4", "notes.pdf"]
submission_id = batch_submit_files(files)

Status transitions

A submission moves through the statuses below. The terminal states are complete and failed.
StatusMeaningAction
uploadingAssets are being receivedWait
submittedSubmission registered and queuedProcessing will start shortly
processingAssets are being analyzedPoll for updates
completeAll assets finishedRetrieve results
failedOne or more assets failedCheck errorMessage / per-asset fields
There is no created, completed, or processed status. Use complete for the success terminal state.

Error handling

If submission creation fails, check these common cases.

Invalid blob path (403)

{ "detail": "..." }
Ensure the file was uploaded successfully and the blobPath matches the value returned by the upload endpoint exactly.

Previous submission not found (404)

{ "detail": "..." }
The previousSubmissionId does not resolve to a submission in your workspace.

Asset already submitted (409)

{ "detail": "..." }
The asset at that blobPath has already been submitted. Use versioning with previousSubmissionId to create a new version.

Request validation failed (422)

{
  "error": "validation_error",
  "message": "Request payload is invalid.",
  "fields": [
    { "field": "assets", "message": "..." }
  ]
}
Fix the listed fields and resubmit. Common causes:
  • sidekickIds missing or empty — at least one sidekick is required (see Choosing sidekicks).
  • A per-asset sidekickIds is not a subset of the submission-level sidekickIds.
  • An asset’s type has no compatible sidekick — e.g. a PDF submitted only with a video sidekick.

Best practices

  • Upload with presigned URLs — request a URL per file and PUT the bytes straight to Azure Blob Storage.
  • Batch related assets — group related files into one submission.
  • Version explicitly — use previousSubmissionId rather than re-submitting the same path.
  • Handle retries — implement exponential backoff for transient failures.
  • Log submission IDs — save the submissionId for audit trails and support.