Updating Marketplace Controls
This API surface is for integration partners building on top of the Rokt network. Rokt ecommerce partners integrating placements on their own checkout should use the Rokt Ecommerce developer docs instead.
This recipe covers ongoing edits to marketplace controls on an already-onboarded merchant. The pattern is: GET, modify locally, PUT the full result.
PUT replaces full state. Controls endpoints (MCL, status) use PUT-replace — there's no PATCH or merge on this surface. Always GET first, modify locally, and PUT the modified full list. Anything you omit from the PUT becomes unblocked. The layout-edit endpoint (PATCH /v1/partnership/accounts/{id}/layouts/{layout_id}) is the one PATCH endpoint in the partnership API — it does not apply to controls. See SET Semantics.
- GET the current state
Fetch the current MCL and capture the
contentHashfor optimistic concurrency in step 3.curl https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT"{
"status": 200,
"error": null,
"message": "ok",
"request_id": "0e3a1b9c-...",
"data": {
"accountId": "<your-account-id>",
"marketplaceControlsListId": "8c6f2a1e-...",
"name": "Acme Network Controls",
"verticals": [ /* current blocks in Rokt taxonomy */ ],
"translatedVerticals": [ /* same list in partner taxonomy */ ],
"domains": [ { "domain": "competitor.example.com", "policy": "Block" } ],
"contentHash": "v7-1a9e3f4c"
}
} - Modify locally
Append a new blocked vertical while preserving everything you already had. Build the request body off the GET response's
translatedVerticals.- javascript
- python
const envelope = await getMcl(accountId); // GET above
const current = envelope.data; // unwrap the envelope
const next = {
name: current.name,
blockedVerticals: [
...current.translatedVerticals.map(v => ({
partnerVerticalId: v.partner_vertical_id,
partnerSubVerticalId: v.partner_sub_vertical_id,
policy: "Block",
position1Policy: "Block",
})),
// append the new one
{ partnerVerticalId: 1500, partnerSubVerticalId: 1612, policy: "Block", position1Policy: "Block" },
],
domains: current.domains,
contentHash: current.contentHash, // pass back for optimistic concurrency
};envelope = get_mcl(account_id) # GET above
current = envelope["data"] # unwrap the envelope
next_body = {
"name": current["name"],
"blockedVerticals": [
{
"partnerVerticalId": v["partner_vertical_id"],
"partnerSubVerticalId": v["partner_sub_vertical_id"],
"policy": "Block",
"position1Policy": "Block",
}
for v in current["translatedVerticals"]
] + [
{"partnerVerticalId": 1500, "partnerSubVerticalId": 1612, "policy": "Block", "position1Policy": "Block"}
],
"domains": current["domains"],
"contentHash": current["contentHash"],
} - PUT the modified list
Send the full desired state. Include
contentHashso the server can reject the write if anyone else updated the row between your GET and your PUT.curl -X PUT https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @next.jsonIf the server returns a
contentHasherror, someone modified the row between your GET and PUT. Re-GET, re-apply your change to the fresh state, and retry. See Idempotency Keys for why you can safely retry with the same key.noteEnd-to-end stale-hash rejection is preview-grade — verified through the schema but not yet observed live on this partner surface. Treat the 409 path as best-effort until GA.
- Optionally dry-run first
Append
?dry_run=trueto validate the payload — full validation and taxonomy translation — without committing. No state is persisted.curl -X PUT 'https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists?dry_run=true' \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @next.jsonnoteWhen you re-call without
dry_runfor the real write, use a fresh Idempotency-Key. The dry-run key is now cached. See Dry-Run Mode. - Verify
GET again and confirm the new vertical is present, your previous blocks are still there, and
domainsis unchanged.curl https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT"
Common gotchasDirect link to Common gotchas
I sent one new vertical and lost all my previous blocks
SET semantics. PUT is desired-state, not a patch. Always GET-modify-PUT the full list. There is no merge on the server. If you only send [{ vertical: 1612 }], you've told the server "1612 is the entire list" and unblocked everything else.
PUT returns 409 with a contentHash error
Someone else (or another instance of your service) modified the row between your GET and your PUT. The contentHash you sent no longer matches the server's current state.
Resolution: re-GET, re-apply your change to the fresh state, and retry the PUT. You can keep the same Idempotency-Key — the server will treat the new payload as a new operation because the prior attempt was rejected, not committed.
Server returns 400 — 'no partnership vertical mapping'
Your partner-taxonomy partnerVerticalId / partnerSubVerticalId pair isn't in Rokt's vertical mapping table. The PUT is rejected atomically — no partial writes.
Resolution: email smb-partnerships@rokt.com with the unmapped IDs and we'll add the row. In the meantime, omit the unmapped vertical from your PUT.
I want to clear all blocks
Send blockedVerticals: [] and domains: []. The server treats an empty array as "no blocks" — that's the SET semantics fixed point.