Estimate a Provider swap over measured usage
POST /v1/analytics/what-if
| Property | Value |
|---|---|
| Operation | create_what_if |
| Group | Call trace and analytics |
| Scope | usage:read |
| Route class | read |
| Idempotency | Idempotency-Key accepted: a retry with the same key replays the first answer |
| Audit | Not audited: A read sent as a POST that changes no state and reads no Call content, exempt by ADR 0012 like the dry runs. |
| Released in | 1.57.0 |
| Phase 0 host | Served (the hosted api pins 1.65.0) |
Estimates what the Organization would have been charged had a substitute Provider and model served the measured quantities of one stage of an Agent's Calls over a window, at the prices in force now (1.57.0). It writes nothing. The answer carries the measured Calls and minutes, the substitute's latency from the Organization's own Turns on it or else Vocal's dated canary measurements, with sample sizes, and whether the Organization could run it. A stage on the Organization's own key shows quantities only. An Agent of another Organization is not found; a substitute equal to the current Provider and model, or a range over 760 days, is refused with validation_failed.
Request
Set VOCAL_API to the base URL of your API and VOCAL_API_KEY to an API key with the usage:read scope (Authentication and keys). Replace each placeholder, such as agt_... or <name>, with your own value.
curl -X POST "$VOCAL_API/v1/analytics/what-if" \
-H "Authorization: Bearer $VOCAL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_...",
"substitution": {
"stage": "stt",
"current_provider": "mock",
"substitute_provider": "mock",
"substitute_model": "<substitute_model>"
}
}'const response = await fetch(`${process.env.VOCAL_API}/v1/analytics/what-if`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VOCAL_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"agent_id": "agt_...",
"substitution": {
"stage": "stt",
"current_provider": "mock",
"substitute_provider": "mock",
"substitute_model": "<substitute_model>"
}
}),
});
console.log(response.status, await response.text());import os
import uuid
import requests
response = requests.post(
f"{os.environ['VOCAL_API']}/v1/analytics/what-if",
headers={
"Authorization": f"Bearer {os.environ['VOCAL_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"agent_id": "agt_...",
"substitution": {
"stage": "stt",
"current_provider": "mock",
"substitute_provider": "mock",
"substitute_model": "<substitute_model>",
},
},
)
print(response.status_code, response.text)Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
idempotency-key | header | string | no | A client-chosen key, kept 24 hours per Organization, environment and route. A replay returns the stored response. |
Request body
Content type application/json, required: WhatIfRequest.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | AgentId | yes | |
agent_version | integer | no | Only the Calls of this version; every version of the Agent when absent. |
from | RequestTimestamp | no | With window=custom, the start of the range, inclusive (1.57.0). |
substitution | WhatIfSubstitutionInput | yes | |
to | RequestTimestamp | no | With window=custom, the end of the range, exclusive (1.57.0). |
window | AnalyticsWindow | no |
Responses
| Status | Content type | Shape | Error codes |
|---|---|---|---|
200 | application/json | WhatIf | |
400 | application/json | ErrorEnvelope | invalid_request |
401 | application/json | ErrorEnvelope | unauthenticated |
403 | application/json | ErrorEnvelope | forbidden |
404 | application/json | ErrorEnvelope | not_found |
422 | application/json | ErrorEnvelope | validation_failed |
429 | application/json | ErrorEnvelope | rate_limited |
500 | application/json | ErrorEnvelope | internal_error |
Fields of the 200 answer
The shape is WhatIf.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | AgentId | yes | |
agent_version | integer or null | yes | |
calls | integer | yes | The Calls whose quantities were measured. |
currency | "USD" | yes | |
current | WhatIfSide | yes | |
delta_micros | integer or null | yes | The substitute amount minus the current amount; null when either is null. |
eligible | boolean | yes | |
estimate | true | yes | |
from | string | yes | |
ineligible_reasons | array of WhatIfIneligibleReason | yes | |
latency | WhatIfLatencyEvidence | yes | |
minutes | number | yes | Their billable Call minutes. |
object | "what_if" | yes | |
price_list_version | string | yes | The version of the price list the estimate used; a date in 1.5.0. |
stage | WhatIfStage | yes | |
substitute | WhatIfSide | yes | |
to | string | yes |