Skip to main content

Overview

The sandbox lets you exercise your integration without running real asset processing or consuming processing credits. It is not a separate host or base URL. You select the sandbox by using a sandbox API key: the same base URL and the same paths behave as a sandbox when the request is authenticated with an sk_test_ key. Sandbox keys unlock two capabilities:
  • A simulationDelaySeconds query parameter on submission creation, to control timing.
  • A POST /api/integrations/sandbox/events/fire endpoint, to dispatch synthetic webhook events to your subscriptions without real processing.
Sandbox submissions are excluded from Insights — their canned results never affect your workspace’s counts, rates, or processing durations.

Using a sandbox key

Use your sk_test_ key against the normal base URL and the normal endpoints. The asset filename selects the outcome to simulate — see Choosing a test scenario. As with live submissions, sidekickIds is required (at least one), though the sandbox does not actually run the named sidekicks.
curl -X POST https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions \
  -H "X-API-Key: sk_test_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{
    "assets": [
      { "blobPath": "11111111-1111-1111-1111-111111111111/__sandbox_pass__" }
    ],
    "sidekickIds": ["<sidekick-id>"]
  }'
A live (sk_live_) key calls the same endpoints but cannot use the sandbox-only features described below.

Choosing a test scenario

Sandbox submissions never run real analysis. Instead, you pick the outcome by embedding a magic marker in the asset’s filename. The marker selects a canned result that flows through the normal read endpoints — submission status, per-asset issues, and retry — exactly as a real processing result would.
Every asset in a sandbox submission must contain one of the markers below in its filename. A sandbox submission whose asset filename has no recognised marker is rejected with 400 Bad Request:
{ "detail": "Sandbox API keys only accept magic filenames. '<filename>' must contain one of: __sandbox_pass__, __sandbox_advisory__, __sandbox_fail__, __sandbox_error__, __sandbox_retryable_error__, __sandbox_pending__. See the sandbox documentation for details." }
Live (sk_live_) keys ignore these markers entirely — they run real processing regardless of filename.
blobPath always has the form {asset-id}/{filename}, where {asset-id} is a UUID. The marker goes in the filename — the part after the /. The simplest form is to use the marker as the whole filename, e.g. 11111111-1111-1111-1111-111111111111/__sandbox_pass__. Because the sandbox never reads a real file, you do not need to upload anything first for sandbox submissions — any UUID works as the asset id. (For live submissions you obtain blobPath from the upload endpoints instead.)
Filename markerSubmission statusAsset outcomeIssues producedRetryable
__sandbox_pass__completeClean passNone
__sandbox_advisory__completePasses with advisories2 — one minor, one info
__sandbox_fail__completeCompletes but carries blocking issues3 — one critical, two major
__sandbox_error__failedNon-retryable processing failureNoneNo
__sandbox_retryable_error__failedTransient processing failureNoneYes
__sandbox_pending__processingStays in processing, never finalisesNone
A few details worth noting:
  • __sandbox_fail__ still reports complete. A “fail” here means the asset finished but surfaced critical/major issues — the submission status is complete and the issues appear via the asset issues endpoint. Use it to exercise issue-handling logic.
  • __sandbox_error__ vs __sandbox_retryable_error__. Both drive the submission to failed. The error variant is a permanent failure (isRetryable: false); the retryable variant sets isRetryable: true so you can exercise the retry endpoint. The submission’s errorMessage is Sandbox simulated error or Sandbox simulated retryable error respectively.
  • __sandbox_pending__ leaves the submission in processing indefinitely — useful for testing polling timeouts and real-time status updates.
  • Mixed-scenario submissions. When a submission contains assets with different markers, the worst outcome decides the overall submission status, in this order (worst first): errorretryable_errorfailadvisorypasspending. Each asset still carries its own issues and per-asset failure fields.

Example: testing the issue path

curl -X POST https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions \
  -H "X-API-Key: sk_test_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{
    "assets": [
      { "blobPath": "11111111-1111-1111-1111-111111111111/__sandbox_fail__" }
    ],
    "sidekickIds": ["<sidekick-id>"]
  }'
Poll the submission to completion, then read the issues for the returned asset — you will get one critical and two major issues to drive your review UI or compliance checks.

Example: testing the retry path

curl -X POST https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions \
  -H "X-API-Key: sk_test_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{
    "assets": [
      { "blobPath": "11111111-1111-1111-1111-111111111111/__sandbox_retryable_error__" }
    ],
    "sidekickIds": ["<sidekick-id>"]
  }'
The submission ends as failed with isRetryable: true. Call the retry endpoint for that asset to exercise your recovery flow: the sandbox simulates a successful retry, so the asset recovers and the submission moves to complete with isRetryable: false. The retry is single-use — once the asset has recovered it is no longer in a failed state, so a second retry returns 409. Submit __sandbox_error__ instead to verify that a non-retryable failure is rejected by the retry endpoint with 409.

Simulating processing time

Add the simulationDelaySeconds query parameter to a submission-create request to delay when the submission reaches a terminal state. This is useful for exercising your polling and webhook-handling logic.
curl -X POST "https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions?simulationDelaySeconds=5" \
  -H "X-API-Key: sk_test_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{ "assets": [ { "blobPath": "11111111-1111-1111-1111-111111111111/__sandbox_pass__" } ] }'
  • The parameter is camelCase: simulationDelaySeconds.
  • Accepted range is 0 to 30 seconds. Default is 0.
  • It works only with a sandbox key. Sending it on a live key returns a 400.

Firing synthetic webhook events

To test your webhook handler end to end, fire a synthetic event for an existing submission’s assets. The endpoint dispatches the event to every matching subscription in your workspace, exactly as a real processing event would, but without running any processing.

POST /api/integrations/sandbox/events/fire

Request body (camelCase):
{
  "eventType": "asset.processing.failed",
  "submissionId": "550e8400-e29b-41d4-a716-446655440000",
  "errorMessage": "Simulated processing failure"
}
FieldRequiredNotes
eventTypeYesOne of asset.processing.completed or asset.processing.failed
submissionIdYesA submission that exists in your workspace
errorMessageNoOptional message, used with asset.processing.failed
Response 200:
{
  "dispatched": 2
}
dispatched is the number of subscriptions notified across all of the submission’s assets. This endpoint is sandbox-only. Behaviour by condition:
ConditionResult
Sandbox key, submission in workspace200 with dispatched count
Live key403
submissionId not found in your workspace404
Malformed request body422

Testing the failure path

There are two complementary ways to exercise failure handling in the sandbox. To test the processing-result read path, submit an asset with the __sandbox_error__ or __sandbox_retryable_error__ marker (see Choosing a test scenario) and read the failure back from the submission status. To test webhook delivery end to end, subscribe a webhook, create a submission, then fire an asset.processing.failed event and confirm your endpoint received it.
import requests

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


def test_failure_webhook(submission_id: str) -> None:
    """Subscribe, fire a synthetic failure event, and confirm dispatch."""
    # 1. Subscribe to the failure event.
    sub = requests.post(
        f"{API_BASE}/api/integrations/webhooks/subscriptions",
        headers={"X-API-Key": api_key},
        json={
            "endpointUrl": "https://your-domain.com/webhooks/test",
            "eventTypes": ["asset.processing.failed"],
            "description": "Sandbox failure test",
        },
    )
    secret = sub.json()["secret"]
    print(f"Subscribed; verify deliveries with secret {secret}")

    # 2. Fire a synthetic failure for an existing submission's assets.
    fired = requests.post(
        f"{API_BASE}/api/integrations/sandbox/events/fire",
        headers={"X-API-Key": api_key},
        json={
            "eventType": "asset.processing.failed",
            "submissionId": submission_id,
            "errorMessage": "Simulated processing failure",
        },
    )

    dispatched = fired.json()["dispatched"]
    assert dispatched >= 1, "expected at least one subscription to be notified"
    print(f"Dispatched failure event to {dispatched} subscription(s)")
    print("Now assert your endpoint received an asset.processing.failed event")
The delivered webhook payload follows the standard shape documented in Webhook subscriptions: an outer type of asset.processing.failed, with a snake_case data object containing asset_id, submission_id, workspace_id, status of "failed", and error_message.

Key differences from production

AspectSandbox (sk_test_ key)Production (sk_live_ key)
SelectionAPI key prefix sk_test_API key prefix sk_live_
Base URL and pathsSame as productionSame as sandbox
ProcessingNo real analysis or credits consumed; outcome chosen by filename markerReal processing
Asset filenameMust contain a __sandbox_*__ scenario markerReal filename, no marker required
Webhook eventsCan be fired synthetically via /sandbox/events/fireEmitted by real processing
Timing controlsimulationDelaySeconds (0–30) on submission createNot available

Best practices

  • Use a sandbox key for all integration testing before switching to a live key.
  • Pick the outcome you want to test with the filename marker — __sandbox_fail__ for issue handling, __sandbox_retryable_error__ for retries, __sandbox_pending__ for polling timeouts.
  • Exercise your webhook handler by firing asset.processing.failed against an existing submission.
  • Use simulationDelaySeconds to test polling and webhook timing.
  • Verify webhook signature handling against the secret returned when you create a subscription.

Troubleshooting

Sandbox submission rejected with 400

  • A sandbox key requires every asset filename to contain a scenario marker (e.g. __sandbox_pass__). Add one of the markers from Choosing a test scenario to the filename portion of blobPath.
  • The marker must appear after the first / in blobPath — that is the filename the sandbox inspects.

Webhook not delivered

  • Ensure your endpoint is publicly reachable and returns 200
  • Confirm the subscription’s eventTypes includes the event you fired
  • Check that dispatched was at least 1 in the fire response
  • Review delivery history via the subscription’s deliveries endpoint

simulationDelaySeconds rejected

  • The parameter is camelCase: simulationDelaySeconds
  • The value must be between 0 and 30
  • A 400 means it was sent on a live key — use a sandbox (sk_test_) key

Fire endpoint returns 403 or 404

  • 403 means you used a live key; sandbox events require a sk_test_ key
  • 404 means the submissionId does not exist in your workspace

Ready for production?

Switch to your live (sk_live_) key against the same endpoints. Create or manage keys in the control plane, or contact support at https://mediamagic-verify.support.site.