Tune API
Read-only access to call-quality metrics across the shops you cover — volume, script scores, booked rate, declined work and attribution. Resource-oriented URLs, query-string filters, JSON responses, standard HTTP status codes.
Overview
Every endpoint returns aggregates. There is no endpoint that lists individual calls, transcripts or customer data, and the scope that would grant that is separate from the one these endpoints use — a key that reports totals cannot enumerate the calls behind them.
Base URL: https://api.tune.srsandco.com
There is no sandbox
The API reads live production data. It is read-only — no endpoint writes anything — so the risk is disclosure rather than damage, but a key should still be scoped to only the stores its consumer needs.
Authentication
Requests authenticate with a long-lived API key sent as a bearer token. There is no token exchange and nothing expires mid-session, so a client that can only attach a fixed header block works without a refresh cycle.
-H "Authorization: Bearer tune_sk_live_..."
A key is scoped twice: by what it may ask for (scopes, below) and by whose datait may see — an account, or an explicit list of stores. Both are checked on every request. Asking for a store outside the key's scope returns a 404, not a 403: existence itself is scoped, so the API never confirms a record it will not return.
Scopes
| Scope | Description |
|---|---|
| metrics:read | Aggregate metrics and the store directory. Everything documented here. |
| calls:readreserved | Individual call records. No endpoint uses this yet — it exists so that granting metrics access never implies it. |
| transcripts:readreserved | Transcript text. Not yet exposed. |
| scores:readreserved | Per-criterion scoring detail. Not yet exposed. |
| outcomes:readreserved | Per-call outcome records. Not yet exposed. |
Versioning
The version is part of the path — /api/v1/… — never a header. Adding fields to a response is not a breaking change and will happen without a version bump, so clients should ignore unknown fields. Removing or renaming one means a new version.
Error responses
Errors carry a stable machine-readable code and a request id. Database messages are never surfaced.
{
"error": {
"code": "invalid_range",
"message": "Range is limited to 400 days.",
"request_id": "req_a57c695953e14f898de5c1ed"
}
}| Status | Code | Meaning |
|---|---|---|
| 200 | — | Everything worked. |
| 400 | invalid_range | from/to unparseable, inverted, or wider than 400 days. |
| 401 | missing_key | No bearer token supplied. |
| 401 | invalid_key | Unknown, revoked or expired key. Deliberately identical for all three — telling an unauthenticated caller which it was tells them whether they found a real key. |
| 403 | missing_scope | Valid key, but it lacks the scope the endpoint requires. |
| 404 | not_found | The resource does not exist, or is outside this key's store scope. The two are the same response on purpose: the API never confirms a record it will not return. |
| 500 | internal_error | Fault on Tune's side. Quote the request_id when reporting it. |
Every response — success or error — carries the same id in an x-request-id header.
Limits
No rate limiting is enforced today
That is stated because it is true, not because the API is unlimited by design. Every request is recorded against its key, so abnormal volume is visible — expect a per-key limit before access widens beyond current integrations.
The one hard bound in place is a 400-day maximum range on the metrics endpoints. They run synchronously behind a user action, not as batch jobs.
Stores
The stores this key can see. Use it to resolve ids before requesting metrics.
Response attributes
| Field | Description |
|---|---|
| storeIduuid | Tune's identifier. Pass to other endpoints. |
| storeNamestring | Full name, e.g. Rooster Ridge Car Care (Pearland). |
| storeNumberstring | null | The shop's own number, where set. |
| timezonestring | IANA zone. Daily buckets are computed in this, not UTC. |
| accountNamestring | null | The business the store belongs to. |
| accountSlugstring | null | URL-safe form of the account name. |
Example
curl 'https://api.tune.srsandco.com/api/v1/stores' \ -H "Authorization: Bearer $TUNE_API_KEY"
{
"stores": [
{
"storeId": "e480609c-d8b6-4f2d-9999-e34ac39255f6",
"storeName": "Rooster Ridge Car Care (Cypress)",
"storeNumber": "1111",
"timezone": "America/Chicago",
"accountName": "Rooster Ridge Car Care",
"accountSlug": "rooster-ridge"
}
]
}Store Metrics
Per-store totals for a date range, with a daily series.
| Parameter | Description |
|---|---|
| fromISO date | Inclusive start. Defaults to 30 days before `to`. |
| toISO date | Exclusive end. Defaults to now. For all of August, pass 2026-09-01. |
Two pipeline fields, not one
recordingUnavailableUpstream is the phone system's own retention expiry — nothing can recover it and it is not a Tune failure. Adding it to recordingFailedOther and plotting the total reports a vendor's retention policy as if it were an error rate.
daily can start a day before from
Buckets are computed in the store's own timezone, so a call at 2026-08-01T00:30Z belongs to July 31st in Central time. That is correct — it is the day the shop had it — but a client charting the series should expect an extra leading bucket.
Response attributes
| Field | Description |
|---|---|
| calls | Every call in range. |
| inbound / outbound | Direction split. May not sum to calls — some calls have no direction recorded. |
| answered | Calls a human picked up. |
| avgDurationSeconds | Mean call length. |
| avgScorenumber | null | Mean script score, 0–100. Null when nothing was scored. |
| scoredCalls | Denominator of avgScore. |
| checkedCalls | Denominator of bookedPct. See “What the numbers mean” — it is not calls. |
| bookedCalls | Checked calls that led to booked work. |
| bookedPctnumber | null | Percentage to one decimal. Null when checkedCalls is 0 — unknown, not zero. |
| authorizedCents | Work the customer approved, in cents. |
| declinedAskedForCents | Declined work matching what the customer came in for. |
| declinedRecommendedCents | Declined work the shop proposed. Inferred, not labelled by the source system. |
| unattributed / unattributedPct | Calls no rep could be matched to. |
| pipelineobject | Processing health: recordingPending, transcriptPending, recordingUnavailableUpstream, recordingFailedOther. |
| callTypesobject | Counts keyed by script type, plus unscored. |
| dailyarray | { day, calls, avgScore } per local day. |
Example
curl 'https://api.tune.srsandco.com/api/v1/metrics/stores?from=2026-08-01&to=2026-09-01' \ -H "Authorization: Bearer $TUNE_API_KEY"
{
"range": { "from": "2026-08-01T00:00:00+00:00", "to": "2026-09-01T00:00:00+00:00" },
"stores": [
{
"storeId": "41b2914a-c26d-426f-a994-5c5b7336961b",
"storeName": "Rooster Ridge Car Care (Pearland)",
"storeNumber": "1714",
"accountName": "Rooster Ridge Car Care",
"accountSlug": "rooster-ridge",
"calls": 2397,
"inbound": 1281,
"outbound": 1116,
"answered": 2397,
"avgDurationSeconds": 162,
"avgScore": 70.5,
"scoredCalls": 1999,
"checkedCalls": 1342,
"bookedCalls": 667,
"bookedPct": 49.7,
"authorizedCents": 61764630,
"declinedAskedForCents": 6153903,
"declinedRecommendedCents": 87104091,
"unattributed": 915,
"unattributedPct": 38.2,
"pipeline": {
"recordingPending": 0,
"transcriptPending": 0,
"recordingUnavailableUpstream": 32,
"recordingFailedOther": 0
},
"callTypes": {
"general": 1544, "other": 323, "isolate_broken": 211,
"anytime": 124, "pmi": 120, "unscored": 75
},
"daily": [
{ "day": "2026-07-31", "calls": 3, "avgScore": 38.3 },
{ "day": "2026-08-01", "calls": 83, "avgScore": 57 }
]
}
]
}Rep Metrics
Per-rep totals — the leaderboard source. Same from/to parameters as store metrics.
One row per rep per store
Someone who worked at two stores in the period appears twice, with their calls filed where the work actually happened. Do not add those rows together and call the result one rep's average — group by employeeId and recompute, or state which store you mean.
Response attributes
| Field | Description |
|---|---|
| employeeIduuid | Stable across store moves. |
| namestring | First name and last initial. No contact details are exposed. |
| storeId / storeName | Where these calls happened, not the rep's home store. |
| calls | Every call attributed to them at this store. |
| scoredCalls | Subset that was scored. Denominator of avgScore. |
| avgScorenumber | null | Mean script score. |
| checkedCalls / bookedCalls / bookedPct | As for stores. |
| confidentAttribution | Calls matched by stated name or assigned by hand, as opposed to inferred. Lower than calls means some attribution is weaker. |
Example
curl 'https://api.tune.srsandco.com/api/v1/metrics/reps?from=2026-08-01&to=2026-09-01' \ -H "Authorization: Bearer $TUNE_API_KEY"
{
"range": { "from": "2026-08-01T00:00:00+00:00", "to": "2026-09-01T00:00:00+00:00" },
"reps": [
{
"employeeId": "0e2f9c0f-67f9-4aec-99f7-a364e5f826b9",
"name": "Bailey C.",
"storeId": "41b2914a-c26d-426f-a994-5c5b7336961b",
"storeName": "Rooster Ridge Car Care (Pearland)",
"calls": 503,
"avgDurationSeconds": 166,
"avgScore": 68.8,
"scoredCalls": 485,
"checkedCalls": 385,
"bookedCalls": 177,
"bookedPct": 46,
"confidentAttribution": 503
}
]
}What the numbers mean
Four things that are easy to get wrong without being told.
Booked rate is not over all calls
bookedPct is bookedCalls / checkedCalls. checkedCalls counts only customer calls whose outcome has actually been checked. Vendor and internal calls are excluded because they cannot book work — including them measured about five points of drag on every shop. Unclassified calls are excluded too: an unknown counterparty is not evidence of a missed sale. Always show the denominator alongside the rate.
Null means unknown, never zero
avgScore and bookedPct are null when there is nothing to average. Rendering that as 0 says “this shop converts nobody”, which is a different and much worse claim than “not enough data yet”.
Small samples are not rankings
A rep with three calls at 100 is not the best rep. Tune's own leaderboards require at least 25 calls before ranking anyone. The API returns raw figures and leaves the threshold to the consumer — so the consumer has to apply one.
Declined work is normal
Declined dollars are mostly work found on inspection rather than what the customer came in asking for, which is why the two are reported separately. A high declinedRecommendedCents means the shop is inspecting and recommending, not that it is failing.
Getting a key
Keys are issued by the Tune team. Ask for one and say which stores or account the integration needs and what it is for — a key is scoped to exactly that, so a narrower request is quicker to grant.
You will receive the key once, in plaintext. Store it as a secret in your deployment environment, never in source control. Tune keeps only a hash of it, so if it is lost it cannot be recovered or re-sent — a replacement has to be issued.
Keys expire after a year by default. Rotation needs no coordinated cutover: ask for the replacement, deploy it, then tell us to revoke the old one. Both work at once until you do.
If a key is exposed, say so immediately rather than waiting for the rotation window. Revocation takes effect on the next request.