Getting Started

Sandbox (dry run)

Simulate any TokPortal API call for free: real validation, real errors, the real price, and nothing created or charged. Send X-TokPortal-Dry-Run: true, or dry_run: true from MCP.

Sandbox (dry run)

Credits are debited when a bundle is created, not when it is published, and there is no cancellation or refund path. That makes the first call to an unfamiliar operation expensive to get wrong.

The sandbox removes that risk. Send one header and any write becomes a simulation: the request runs the same validation and the same server-side pricing as the real call, then stops before the first write.

curl -X POST https://app.tokportal.com/api/ext/bundles \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "X-TokPortal-Dry-Run: true" \
  -H "Content-Type: application/json" \
  -d '{
    "bundle_type": "account_and_videos",
    "platform": "tiktok",
    "country": "USA",
    "videos_quantity": 3,
    "edits_quantity": 0,
    "wants_advanced_warming": false
  }'
{
  "data": {
    "bundle_id": "00000000-0000-4000-8000-6b5f697784b1",
    "bundle_type": "account_and_videos",
    "platform": "tiktok",
    "country": "USA",
    "status": "pending_setup",
    "videos_quantity": 3,
    "review_mode": "auto",
    "created_at": "2026-09-01T13:43:56.863Z"
  },
  "credits_charged": 0,
  "credits_would_charge": 38,
  "credits_remaining": 11823,
  "cost_breakdown": {
    "account_creation": 32,
    "video_slots": 6,
    "edit_slots": 0,
    "advanced_warming": 0,
    "total": 38
  },
  "dry_run": true,
  "dry_run_notice": "Simulated: nothing was created, no credits were debited, and the returned identifiers do not exist. Repeat this call without the X-TokPortal-Dry-Run header to execute it for real."
}

201 Created, and your balance is untouched.

How to ask for it

SurfaceHow
RESTHeader X-TokPortal-Dry-Run: true (also accepts 1 and yes, case-insensitive)
MCPTop-level tool argument dry_run: true — the server converts it to the header
SDKs / CLIPass the header through your client's request options

Any other value, including false and 0, means a real call. So does a missing header.

On GET requests the header is ignored. Reads are free and change nothing, so a client that sets the header globally keeps working.

What a dry run returns

Everything a real call returns, in the same shape, with the same HTTP status — plus:

FieldMeaning
dry_runAlways true. Absent — never false — on a real call.
dry_run_noticeOne sentence restating what did and did not happen, for an agent to relay to a person.
credits_would_chargeThe real price this call would have cost, your workspace allowance included. This is the number to confirm with a human before repeating the call for real.
credits_chargedAlways 0. Nothing was debited, so a client summing its spend must not count simulations.
credits_remainingYour real, current, unchanged balance.

Every response — success and error — also carries the header X-TokPortal-Dry-Run: true. On errors that header is the only marker, so check it there rather than parsing the body.

Errors are identical, on purpose

A dry run does not soften anything. An invalid request fails with exactly the code, status and details it would fail with for real:

curl -X POST https://app.tokportal.com/api/ext/bundles \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "X-TokPortal-Dry-Run: true" \
  -H "Content-Type: application/json" \
  -d '{"bundle_type":"account_only","platform":"tiktok","country":"USA","advanced_warming_terms":["fitness","gym","protein"]}'
{
  "error": {
    "code": "ADVANCED_WARMING_FLAG_REQUIRED",
    "message": "Advanced Niche Warming targets were supplied without opting in. Add \"wants_advanced_warming\": true to the same request body, next to advanced_warming_terms / advanced_warming_terms_count. The flag is mandatory: it is what is priced and what creates the warming session, and TokPortal never infers it. Check \"details.supplied_fields\" for the fields that triggered this.",
    "details": {
      "supplied_fields": ["advanced_warming_terms"],
      "wants_advanced_warming": false,
      "fix": "Re-send the same body with \"wants_advanced_warming\": true added."
    }
  }
}

That is the point of the sandbox: learn what an operation refuses before a real call refuses it and you have to reason about whether you were charged.

Chaining a whole flow

Identifiers returned by a dry run are synthetic. They are valid UUIDs, they always start with 00000000-0000-4000-8000-, and they exist only inside dry-run calls — where they are accepted so you can walk an entire flow:

# 1. Simulate the creation and keep the synthetic id
BUNDLE=00000000-0000-4000-8000-6b5f697784b1

# 2. Simulate configuring its account
curl -X PUT "https://app.tokportal.com/api/ext/bundles/$BUNDLE/account" \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "X-TokPortal-Dry-Run: true" \
  -H "Content-Type: application/json" \
  -d '{"username":"my_brand_01","visible_name":"My Brand","niche":"fitness"}'

# 3. Simulate publishing it
curl -X POST "https://app.tokportal.com/api/ext/bundles/$BUNDLE/publish" \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "X-TokPortal-Dry-Run: true" \
  -H "Content-Type: application/json" -d '{}'

Send a synthetic identifier to a real call and it is refused immediately, rather than surfacing as a confusing not-found:

{
  "error": {
    "code": "DRY_RUN_ID_IN_LIVE_REQUEST",
    "message": "This identifier was produced by a dry run and does not exist. Re-run the creation call without the X-TokPortal-Dry-Run header to obtain a real one.",
    "details": { "parameter": "id", "value": "00000000-0000-4000-8000-6b5f697784b1" }
  }
}

NOTE: Simulated objects have no memory A synthetic bundle does not remember the videos_quantity you created it with. Each call is validated fully on its own; cross-call consistency is not simulated. Use the sandbox to learn shapes, prices and errors — not to predict how a specific bundle will behave three steps later.

Guarantees

In dry-run mode, none of this happens:

  • No credits debited, and no contract allowance slot consumed — though the price reflects the allowance.
  • No row written, in any table.
  • No webhook delivered.
  • No notification, no SMS, no email: no account manager ever sees a simulated bundle.
  • No secret revealed. revealAccountCredentials and retrieveAccountVerificationCode return their 428 policy preview and nothing else — never a credential, real or invented — and the account's Coverage is untouched.

And this still happens, exactly as in a real call:

  • Authentication, key scopes, expiry and revocation.
  • Every business validation, in the same order, with the same codes.
  • Rate limits. A dry run consumes the same bucket as a real call: the server work is real even when the write is not. Do not loop dry runs.

Your Idempotency-Key survives

An Idempotency-Key sent alongside a dry run is ignored, not consumed. The real call that follows with the same key executes normally rather than replaying the simulated response.

# Simulate with a key…
curl -X POST https://app.tokportal.com/api/ext/bundles \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "Idempotency-Key: launch-2026-09-01-a" \
  -H "X-TokPortal-Dry-Run: true" \
  -H "Content-Type: application/json" -d "$BODY"

# …then execute for real with the SAME key. This creates the bundle.
curl -X POST https://app.tokportal.com/api/ext/bundles \
  -H "X-API-Key: $TOKPORTAL_API_KEY" \
  -H "Idempotency-Key: launch-2026-09-01-a" \
  -H "Content-Type: application/json" -d "$BODY"

From an agent

Every non-GET MCP tool takes dry_run as a top-level argument:

{
  "name": "tokportal_create_bundle",
  "arguments": {
    "dry_run": true,
    "body": {
      "bundle_type": "account_and_videos",
      "platform": "tiktok",
      "country": "USA",
      "videos_quantity": 3,
      "edits_quantity": 0,
      "wants_advanced_warming": false
    }
  }
}

The recommended pattern for an agent acting on someone's behalf: dry-run first, show the human credits_would_charge, then repeat the call for real once they agree. It costs nothing and removes the only irreversible decision in the flow.

dry_run must be a real boolean. The string "true" is refused rather than silently executed for real.

  • Credits — what each operation costs and when it is debited
  • Errors — the full error-code reference
  • Rate limits — the buckets a dry run also consumes
  • MCP server — installing the server that exposes dry_run as a tool argument