Hours and caps
Set when a campaign and each target take calls, and how many: the schedule shape, concurrency reserved at the dial, the hourly, daily, monthly and lifetime windows that roll in UTC, and how buyer caps sit above target caps.
After this guide you will be able to write a schedule that says what you mean — including a split shift and a night shift that crosses midnight — put caps at the right level, say exactly which call moves which counter, and read a target's usage against its caps without guessing when the count resets.
Hours say *when* calls are taken and caps say *how many*. They exist because a buyer who is sent a call at three in the morning, or a hundred more than they agreed to pay for, stops being a buyer. Both are set at more than one level, and a call has to pass every level that applies to it. When one does not, the call's waterfall says which level, which window and what the limit was.
Where limits live
| Level | Hours | Caps | What happens when it stops a call |
|---|---|---|---|
| Campaign | Yes, on the workspace's clock | Calls in progress, calls a day | The call is not routed: campaign_closed or campaign_capped, and the caller gets the unrouted action. |
| Buyer | — | Concurrency, hourly, daily, monthly, daily revenue | Every target that buyer owns is skipped with buyer_capped. The call carries on down the plan. |
| Target | Yes, in its own zone | Concurrency, hourly, daily, monthly, lifetime, daily revenue | That target is skipped with closed, concurrency, capped or spend_capped. |
| Publisher lane | Yes, in its own zone | Concurrency, hourly, daily, monthly, daily payout | A publisher's ping is refused when the lane is full and it is set to block. A call dialled on one of their numbers is counted on the lane and never refused. See Payout terms. |
Hours of operation
A schedule is a time zone and seven days, each day a list of open windows. No schedule at all means always open — which is why a target nobody has ever opened the setting on is still considered at any hour.
{
"tz": "America/Chicago",
"days": {
"mon": [
[
"08:00",
"20:00"
]
],
"tue": [
[
"08:00",
"20:00"
]
],
"wed": [
[
"08:00",
"20:00"
]
],
"thu": [
[
"08:00",
"20:00"
]
],
"fri": [
[
"08:00",
"20:00"
]
],
"sat": [
[
"09:00",
"13:00"
]
],
"sun": []
}
}| Rule | Detail |
|---|---|
| The day keys | mon, tue, wed, thu, fri, sat, sun. Every day is written out in the stored schedule. An unknown key is a 400. |
| A window | ["HH:MM", "HH:MM"] on a 24-hour clock, or { "from": …, "to": … }. The start is included and the end is not, so ["08:00", "20:00"] is open at 19:59 and closed at 20:00. |
| Windows a day | Up to 6. An empty list closes that day. |
| Closing at midnight | "24:00" is allowed, as a closing time only. |
| Crossing midnight | A window whose end is before its start wraps: ["22:00", "02:00"] is open from ten at night until two the next morning. |
| Closed all week | Refused. A schedule that closes all seven days is a target nobody can reach — send null instead, which means always open, or pause the target. |
| The zone | tz inside the schedule wins. Without it the row's own timezone is used, then the workspace's, then America/New_York. |
| An unrecognised zone | Treated as open. A mistyped zone is a configuration error to fix, not a reason to refuse a live caller. |
Two schedules worth copying
A buyer who takes a lunch break has two windows on the same day. Nothing is open between them, and a call arriving at one o'clock skips that target with closed:
{
"tz": "America/New_York",
"days": {
"mon": [
[
"09:00",
"12:30"
],
[
"13:30",
"18:00"
]
],
"tue": [
[
"09:00",
"12:30"
],
[
"13:30",
"18:00"
]
],
"wed": [
[
"09:00",
"12:30"
],
[
"13:30",
"18:00"
]
],
"thu": [
[
"09:00",
"12:30"
],
[
"13:30",
"18:00"
]
],
"fri": [
[
"09:00",
"12:30"
],
[
"13:30",
"17:00"
]
],
"sat": [],
"sun": []
}
}A night desk is written on the day the shift starts. Monday's window runs to two on Tuesday morning, and Saturday's stops at midnight rather than running into an unstaffed Sunday:
{
"tz": "America/Los_Angeles",
"days": {
"mon": [
[
"22:00",
"02:00"
]
],
"tue": [
[
"22:00",
"02:00"
]
],
"wed": [
[
"22:00",
"02:00"
]
],
"thu": [
[
"22:00",
"02:00"
]
],
"fri": [
[
"22:00",
"02:00"
]
],
"sat": [
[
"22:00",
"24:00"
]
],
"sun": []
}
}In the console, a campaign's schedule is under Settings in the When it takes calls card, labelled Hours of operation, and a target's is in the When it is open section of its editor. A target row shows an Open or Closed pill, with the next change on hover; Always open means it has no schedule.
How caps are counted
There are three kinds of limit, and they behave differently on purpose.
| Kind | What it counts | How it is enforced |
|---|---|---|
| Concurrency | Calls in progress on this target, buyer or line right now. | Reserved, atomically, at the moment of the dial. |
| Hourly, daily, monthly, lifetime | Calls inside the current window. | Counted when the call reaches the moment the target names. |
| Daily revenue | Money booked against this target or buyer today. | Counted at settlement, when there is any money to count. |
Concurrency is reserved, not read
Taking a concurrent slot is a single conditional update — *add one, if there is room* — so two calls arriving in the same millisecond cannot both take the last slot. Exactly one wins it; the other sees concurrency against that target and moves down the plan. A slot is taken on the target, then its buyer, then the target's line, and it is all three or none: if the buyer is full, the target's slot is handed straight back.
- The slot is released when that leg ends, however the call ends, and releasing twice is harmless.
- A cap of
0still counts live calls — it simply never refuses one — so the live figure on the console is true either way. - The cap you see tested while the plan is judged is a read, not a promise. A target can look free and be full a moment later; the guarantee is made at the dial.
Window caps count once, at the moment you choose
A target's A cap counts a call setting (capCountsOn) decides which moment of a call's life moves its hourly, daily, monthly and lifetime counters. It is asked once per call, and exactly one moment answers yes — so a call is never counted twice, and never by a setting that changed while it was in progress.
| Setting | Console label | A call counts | Choose it when |
|---|---|---|---|
claimed | Every dial | The moment the target is dialled, answered or not. | The buyer's cap is about how many calls you may send, and a rung phone costs them something. |
connected | Connected calls | When the buyer's leg answers. The default. | The cap is about how many calls the buyer can handle. |
converted | Converted calls | At settlement, when the call becomes billable. | The cap is about how many calls the buyer will pay for. |
- Money is different. Revenue counts against a daily revenue cap at
convertedand nowhere else, whatevercapCountsOnsays, because that is the only moment there is any money. - The lifetime cap (
totalCap) uses the same moment and never resets. It is the one cap a reset is usually meant for. - A call counted against a target is counted against its buyer at the same moment, for the same call.
The campaign's own caps are counted, not reserved
A campaign carries two caps — calls in progress and calls a day — and they are counted at the gate rather than reserved, because a campaign has no slot to take. Two callers landing together can therefore both pass a cap with one place left. That is deliberate: these are your own throttle on your own traffic, while the caps that are a contract with somebody else are reserved.
- The daily figure counts this campaign's calls since midnight on the workspace's clock, and only those that reached a buyer or are still in progress. A call that rang out and ended unanswered does not use up the day.
- The concurrency figure counts this campaign's calls that are still live.
- Either one over its limit ends the call with
campaign_capped, and the caller gets the unrouted action.
Windows roll over in UTC
A target's, a buyer's and a lane's window counters keep a count and a stamp saying which window the count belongs to. The hour is the UTC clock hour, the day is the UTC date and the month starts on the 1st, UTC — whatever time zone your workspace, your buyer or their hours are in.
| Window | Console label | Resets |
|---|---|---|
| Hourly | Per hour | At the top of the UTC hour. |
| Daily | Per day | At midnight UTC. |
| Monthly | Per month | On the 1st, UTC. |
| Lifetime | Lifetime | Never. |
| Daily revenue | Daily revenue | With the day, at midnight UTC. |
Nothing sweeps the counters at midnight. The write rolls the window: the same statement that adds a call either adds to the current count or starts a new count at 1 and moves the stamp. And a read treats a stale window as zero, so a target nobody has called today reads as 0 today without anybody having written that down. Two calls landing either side of midnight both count, and neither erases the other.
The concurrency counters are different: they are not a window, so an hourly job rebuilds them from the calls themselves. A slot that was never given back — a process that died mid-call — is corrected within the hour rather than leaving a target wedged one call under its cap for good.
Buyer caps versus target caps
A buyer's caps hold across every target that buyer owns; a target's hold for that destination alone. They are both tested, and whichever is tighter stops the call. Put a limit on the buyer when it is the company's budget, and on the target when it is one room's phone lines.
| Cap | On a target | On a buyer |
|---|---|---|
| Concurrency | Yes — skip reason concurrency | Yes — skip reason buyer_capped |
| Hourly, daily, monthly | Yes — capped | Yes — buyer_capped |
| Lifetime | Yes — capped | — |
| Daily revenue | Yes — spend_capped | Yes — buyer_capped |
buyer_capped says.Worked example: one buyer, five lines, forty calls a day
Acme Health will take five calls at once, wants no more than forty connected calls a day and stops buying once the day's revenue reaches $1,500. Their Dallas floor is one target of theirs:
{
"name": "Acme Health — Dallas floor",
"concurrencyCap": 5,
"hourlyCap": 0,
"dailyCap": 40,
"monthlyCap": 800,
"totalCap": 0,
"dailyRevenueCapCents": 150000,
"capCountsOn": "connected"
}- At 14:02 UTC the target has three calls in progress and 38 connected today. A caller arrives: the target passes every test, a slot is taken (four of five), and the phone rings.
- The buyer's side answers. Now the call counts —
capCountsOnisconnected— so the day goes to 39 and the hour to 8, on the target and on Acme Health together. - The call converts at 90 seconds and books $42.00. The day's revenue goes to $1,470 — still under the cap.
- The fortieth connected call of the day takes the day count to 40. From that moment every call skips this target with
capped, and the waterfall says so in full: "The target Acme Health — Dallas floor has reached its daily cap of 40 calls." The plan carries on to whoever is next. - A caller two minutes later, with five calls already up, would have seen
concurrencyinstead — a different reason, a different fix. - At midnight UTC nothing happens. The first call of the next UTC day writes
1against the new day and moves the stamp, and the count reads 1.
While all that is going on, the target's usage block is what the console draws its bars from:
{
"usage": {
"liveCalls": 3,
"hour": 7,
"day": 38,
"month": 402,
"total": 5711,
"dayRevenueCents": 142800
},
"liveCalls": 3
}liveCallsagainstconcurrencyCap— three of five.dayagainstdailyCap— 38 of 40, so the bar is red.dayRevenueCentsagainstdailyRevenueCapCents— $1,428 of $1,500.totalhas no cap here, so it is a count and not a limit.
Clearing a target's counters
Reset caps on a target zeroes its hour, day, month and lifetime call counts and the day's revenue, and starts each window now. It is what you want after a test run has filled a daily cap with calls that were never real.
- It does not touch
liveCalls. Calls that are genuinely in progress stay counted, and a stuck slot is corrected by the hourly rebuild instead. - It does not touch the caps themselves, and it does not touch the buyer's counters — a buyer has no reset of its own. If a buyer's day has to be let through, raise the cap.
- It cannot be undone, so the response and the activity log both record what the counters read before.
curl -X POST "https://api.buy3.io/api/v1/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d/reset-caps" \
-H "Authorization: Bearer $BUY3_API_KEY"An API key with targets:write can reset counters and change a target's caps, hours and states — never where its calls go. The full request and response are in the Targets API reference.
When a cap is not what you wanted
| What you see | What it usually is |
|---|---|
A target skipped with capped all afternoon | A daily cap that filled on the UTC day, not yours. Check usage.day against dailyCap. |
A target skipped with concurrency while its phones are free | Slots that were taken and never released — or genuinely simultaneous calls. The hourly rebuild corrects the first within the hour. |
| Every target of one buyer skipped at once | A buyer cap, or a paused buyer. buyer_capped and buyer_paused are different reasons. |
Calls ending as campaign_capped | The campaign's own daily cap or concurrency, counted on the workspace's clock. |
A target skipped with closed when somebody is there | The schedule's zone. tz inside the schedule beats the target's timezone field. |
