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

ScopeDescription
metrics:readAggregate metrics and the store directory. Everything documented here.
calls:readreservedIndividual call records. No endpoint uses this yet — it exists so that granting metrics access never implies it.
transcripts:readreservedTranscript text. Not yet exposed.
scores:readreservedPer-criterion scoring detail. Not yet exposed.
outcomes:readreservedPer-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"
  }
}
StatusCodeMeaning
200Everything worked.
400invalid_rangefrom/to unparseable, inverted, or wider than 400 days.
401missing_keyNo bearer token supplied.
401invalid_keyUnknown, revoked or expired key. Deliberately identical for all three — telling an unauthenticated caller which it was tells them whether they found a real key.
403missing_scopeValid key, but it lacks the scope the endpoint requires.
404not_foundThe 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.
500internal_errorFault 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.

GET/api/v1/storesmetrics:read

Response attributes

FieldDescription
storeIduuidTune's identifier. Pass to other endpoints.
storeNamestringFull name, e.g. Rooster Ridge Car Care (Pearland).
storeNumberstring | nullThe shop's own number, where set.
timezonestringIANA zone. Daily buckets are computed in this, not UTC.
accountNamestring | nullThe business the store belongs to.
accountSlugstring | nullURL-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.

GET/api/v1/metrics/storesmetrics:read
ParameterDescription
fromISO dateInclusive start. Defaults to 30 days before `to`.
toISO dateExclusive 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

FieldDescription
callsEvery call in range.
inbound / outboundDirection split. May not sum to calls — some calls have no direction recorded.
answeredCalls a human picked up.
avgDurationSecondsMean call length.
avgScorenumber | nullMean script score, 0–100. Null when nothing was scored.
scoredCallsDenominator of avgScore.
checkedCallsDenominator of bookedPct. See “What the numbers mean” — it is not calls.
bookedCallsChecked calls that led to booked work.
bookedPctnumber | nullPercentage to one decimal. Null when checkedCalls is 0 — unknown, not zero.
authorizedCentsWork the customer approved, in cents.
declinedAskedForCentsDeclined work matching what the customer came in for.
declinedRecommendedCentsDeclined work the shop proposed. Inferred, not labelled by the source system.
unattributed / unattributedPctCalls no rep could be matched to.
pipelineobjectProcessing health: recordingPending, transcriptPending, recordingUnavailableUpstream, recordingFailedOther.
callTypesobjectCounts 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.

GET/api/v1/metrics/repsmetrics:read

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

FieldDescription
employeeIduuidStable across store moves.
namestringFirst name and last initial. No contact details are exposed.
storeId / storeNameWhere these calls happened, not the rep's home store.
callsEvery call attributed to them at this store.
scoredCallsSubset that was scored. Denominator of avgScore.
avgScorenumber | nullMean script score.
checkedCalls / bookedCalls / bookedPctAs for stores.
confidentAttributionCalls 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.