Overview

Every AI Pricing Model is already running a traffic distribution. From the moment you launch one, it holds an AI Model row set to the weight you chose at launch and a Baseline row holding the remainder, and every paywall request that reaches the model is split between those two rows. Traffic Control is where you see that dial and change it.

Changing it means more than moving the two starting rows. You can hand a share of the model's traffic to a placement that is running an A/B test, which gives you a genuine user-level test running alongside the model, and you can schedule a different split to take over for a fixed window and hand traffic back on its own.

To open it, click AI Pricing Models in the left sidebar, open a model, and select the Traffic Control tab. You do not have to create a distribution first. Every model already has one, so the tab opens on the split the model launched with, ready to read or edit. Traffic Control is available on Pro, Pro Plus and Enterprise plans. Owner and Admin seats can change the distribution, and Viewer seats can read it.

What you can do here

  • Cap how much traffic the model decides. Lower the AI Model row and give the remainder to the model's baseline paywall. See the three allocation targets.

  • Run a real A/B test on part of your traffic. Point a row at a placement that is running a live A/B test and give it the share you want. The users in that share are picked consistently, each of them keeps the same variant, and the answer carries the test's ID, so the arm can be attributed. See A/B Tests in Traffic Control.

  • Hold a slice back for creatives you are not ready to hand to the model. Put the candidates behind their own placement, run them there as an A/B test, and give that placement the share you want to hold back. See Create Placements.

  • Schedule a different split that reverts on its own. A scheduled distribution replaces the always-on one for the window you set, then hands traffic back automatically. See Scheduled Distributions.

The three allocation targets

A distribution is a list of rows. Each row has a target and a weight, and the weights must total exactly 100 before the distribution can be saved. There are three kinds of target.

TargetWhat it serves
AI ModelHands the request to the AI decision service, which picks one of the paywall variants attached to this model. Within this model it is the only target that sets isExperiment: true on the response.
BaselineThe paywall currently set as the model's Baseline Paywall, which is the single paywall in the model flagged as best. Baseline is not a separate object, it is that one paywall, so the row on screen carries that paywall's own name and a Paywall chip rather than the word Baseline. Changing the model's Baseline Paywall changes what this row serves.
PlacementThe A/B test running on that other placement. A placement can only be saved as a target if it has one all-users audience running a live A/B test, so this row is how a share of the model's traffic goes into a real test. The target's audience is resolved in place, the variant is bucketed on the same profile ID the model was called with, and the answer carries abTestId so it can be attributed to that test. The row resolves once and stops, so it can never hand the request on to another distribution.

That last row is the flexible one, and it is where a test lives. Because the row points at a placement rather than at a paywall, you keep editing the test on the placement, the model keeps the rest of the traffic, and neither side has to change which paywalls it runs. Running an A/B test on a share of traffic is covered in full on A/B Tests in Traffic Control.

A placement row needs a live A/B test on the target. The target placement must have exactly one audience, that audience must be the all-users audience rather than a segment, and it must be running a live A/B test. The picker does not filter the list for you, so choose a placement whose chip reads A/B Test and whose test is running. Anything else is refused when you save and nothing is written. On this always-on distribution the on-screen error reads only Failed to save distribution, so if a save stops working right after you add a placement row, that placement is almost certainly the reason.

You always point an allocation at a placement, never at a paywall ID. Traffic Control has no paywall target. If you want a specific creative in the mix, put it behind a placement first. See Create Placements.

Screenshot of the Traffic Control tab showing a Current Distribution with three allocation rows: AI Model at 50, the baseline paywall tagged Paywall at 20, and a placement tagged A/B Test at 30, above a stacked allocation bar with a Total: 100% readout and an Active badge, alongside a right-hand panel headed 'Your distribution is allocated' with a Scoped to section and an Add Subplacement button.

The distribution above uses all three targets: the model decides 50% of requests, the model's baseline paywall serves 20%, and a placement that is currently routing to an A/B test serves 30%. The stacked bar is the same split drawn to scale, Total: 100% confirms the distribution can be saved, and the Active badge means this is the split serving right now. Selecting a row fills the right-hand panel, where Scoped to names the placement the allocation resolves through. + Add Subplacement adds another placement row.

The always-on distribution and scheduled distributions

Every model has exactly one always-on distribution. It is the one created with the model, it has no end date, and it is the split the model always returns to. Editing it is how you change the model's steady state.

Scheduled Distributions are additional splits, each with its own start and end. While a schedule's window contains the current moment, that schedule fully replaces the always-on distribution. Nothing is blended, and the always-on weights are ignored for the duration.

  • The start time is inclusive and the end time is exclusive.

  • At the end of the window, traffic reverts to the always-on distribution automatically, on the very next request. Nothing needs to be switched back.

  • No background job runs. The active window is worked out per request against the server clock, so a schedule that has just started or just ended takes effect immediately.

  • Only one scheduled window may cover any given moment. A save that would create an overlap is rejected, and a schedule left without an end date counts as covering everything after its start, so it blocks every later schedule.

  • Schedule dates and times are entered and displayed in your browser's local timezone.

Setting one up, including the constraints to plan around, is covered on Scheduled Distributions.

The Current Distribution heading is positional. While nothing is scheduled, the always-on distribution is titled Current Distribution. While a schedule is running, that section is retitled Default Distribution and marked Paused, and a second section titled Current Distribution appears for the schedule that is actually serving. Read Current Distribution as a label for whatever is serving right now, not as a fixed name for one distribution.

How the split is applied

The draw runs in two stages, and they behave differently on purpose. Stage one decides which users belong to a placement row, and it is stable per user. Stage two splits the traffic that is left between the model's own two rows, and it is per request.

Stage one, who is in a placement row. When a request carries a profile ID and the distribution has at least one placement row, Botsi hashes that profile ID against a seed taken from the distribution itself and turns it into a number between 0 and 1. The placement rows occupy the first part of that range, in the order they appear in the distribution, each one as wide as its weight is a share of the total. If the number falls inside a placement row's span, the request goes to that row. The hash is arithmetic, not a lookup, so the same profile gets the same answer on every request, and nothing is stored: there is no assignment table to keep in step with your edits and no cohort to fill up, but there is still a reproducible set of users in the test.

Stage two, the traffic that is left. A profile whose number falls outside every placement span never reaches one. Its requests draw between the model's own rows, AI Model and Baseline, weighted by those rows' numbers, on every request. A user in that leftover traffic can be handed to the model on one fetch and to the baseline on the next, which is the intended behaviour, since both of those arms belong to the same model.

The numbers on the rows are still the shares served. Take 30 to a placement, 50 to AI Model, 20 to Baseline. 30% of profiles sit in the placement span. The remaining 70% draw between 50 and 20, which normalises to five sevenths and two sevenths, so they serve 50% and 20% of all traffic. Nothing about reading the split changes.

No profile ID means no stable subject. Stage one needs someone to hash. A request that arrives without a profile ID has nobody to hash, so there is no stage one for it: the whole draw becomes a single per-request weighted pick across every row, placement rows included. Such a request can land on a placement row, and the same caller can land somewhere else on the next request. Inside the placement row the same thing applies to the A/B test itself, which has no subject to bucket and picks a variant per request. Sending a profile ID on every paywall request is what makes the split user-level, so send one.

What stays stable when you edit:

  • Re-saving the distribution does not reshuffle anyone. Saving rewrites the rows, but the seed comes from the distribution rather than from the rows, so the number computed for each profile is unchanged. Membership moves only where you move a boundary by changing a weight.

  • Changing a weight moves the boundary, not the users. Widening a placement row from 30 to 60 keeps every profile already in it and adds more from the leftover traffic, so nobody is moved out. Narrowing it releases the profiles nearest the boundary and keeps the rest. With more than one placement row the spans sit side by side in row order, so changing an earlier row's weight shifts the spans after it.

  • A scheduled distribution draws its own membership. The seed is per distribution, so a schedule has a different set of users in its placement rows than the always-on split has. Deleting a schedule and recreating it draws again.

  • The variant a user sees is stable too. Inside the A/B test the variant is bucketed on the test's own salt together with the same profile ID, so a user keeps their variant for the life of the test. Traffic Control decides how much traffic reaches the test, it has no influence on which variant a request lands on. Relaunching the test mints a new salt, which reshuffles everyone deliberately.

Weights are read at serve time as a share of their total, and a save cannot complete unless they total 100, so the number on the row and the share it serves are the same. If something changes the rows after the save and the total drifts off 100, the remaining rows are renormalised at serve time and the effective split stops matching the numbers on screen. The two ways that happens are listed in Good to know.

There is no warm-up and no drain in either stage. A saved change applies from the next request, and no traffic is left behind on the previous numbers.

Good to know

  • Weights must total exactly 100. Save Changes stays disabled until they do, and the total is shown above the distribution as you edit.

  • Allocations target placements, not paywalls. There is no paywall target anywhere in Traffic Control, and a placement can only be used as a target while it is running a live A/B test on a single all-users audience. Put the creatives behind a placement, run them there, and point the row at that placement.

  • The AI Model and Baseline rows cannot be removed, only re-weighted. Neither row has a delete control. To take one out of the split, set its weight to 0, which is a valid saved value.

  • A baseline at 0 can still serve. The baseline paywall is also what the AI Model target falls back to whenever it cannot get a decision, so it can be served even when its own row is set to 0.

  • A request with no profile ID never reaches the AI Model target. That request serves the baseline paywall instead, whatever the weights say. The same fallback applies if the decision service errors, times out, or returns nothing. Because the fallback is the baseline, a model with no paywall flagged as best has nothing to fall back to and the request fails.

  • The answer says which arm served it. The model's own arms return aiPricingModelId, with isExperiment: true on an AI decision and false on the baseline. A placement row that served an A/B test returns abTestId instead and suppresses aiPricingModelId, so one answer carries one identity. The Web API response also includes abTestName; the mobile SDK response carries abTestId without the name.

  • Stopping the test does not stop the row. A live test is required when you save the row, but it is not re-checked at serve time. If you stop the test and leave the row in place, that share keeps being served the test's variants, bucketed exactly as before. Re-weight the row to 0 or remove it to take the traffic back. If the target placement is left with no audience at all, requests drawn to that row fail rather than falling back.

  • A placement row resolves once and stops. If the target has drifted since you saved it, serving stays forgiving: a plain paywall is served as it is, and a placement that is now owned by another AI Pricing Model serves that model's baseline paywall rather than entering that model's distribution. That is also why a placement row can never chain on into another distribution.

  • Deleting a placement removes any allocation pointing at it. The row disappears with the placement and nothing revalidates the distribution, which leaves it totalling less than 100 and the remaining rows renormalised at serve time, including the span a surviving placement row covers. Reopen Traffic Control after any placement cleanup and re-check the numbers.

  • Editing the model's weight rewrites part of the split. Changing the weight on the model edit screen rewrites the AI Model and baseline rows of the always-on distribution and leaves any placement row untouched, so a distribution containing a placement row can end up totalling more than 100 and is renormalised at serve time. Reopen Traffic Control after any model weight edit and re-check the numbers.

  • Saving the always-on distribution writes back to the model. The paywall flagged as best keeps that flag and has its stored weight rewritten from the weights you just saved. The model's own weight field is not updated, so it goes stale after a Traffic Control edit, and the next edit on the model screen rebuilds the AI Model and baseline rows from that stale number. Scheduled saves do not do this. Treat the two screens as one setting.

  • The model's own charts do not break the arms out yet. There is no per-arm column in the model's reporting, so read a placement arm in the A/B test's own results and in the paywalls that placement serves. The identity fields above are what tie an individual request to the right arm.

Botsi also has placement-level A/B testing, configured on a placement rather than inside a model. It is the sibling feature for a different job, and the two are easy to tell apart:

  • Use placement-level A/B testing on its own when the test is the whole story for that placement. Every request matching the audience goes into the test, you set the variants and their weights on the placement, and you read the results per variant. See A/B Tests Overview and Perform an A/B Test.

  • Use Traffic Control when an AI Pricing Model owns the placement and you want to decide what share of its traffic goes where, including sending a share to a placement that is running an A/B test.

They also work together. A Traffic Control row pointed at a placement that runs an A/B test gives you both at once: the model on most of your traffic, and a genuine user-level test on the share you set aside, with a stable set of users in it, a stable variant each, and abTestId on every answer it serves. See A/B Tests in Traffic Control.

See also