← Back to Open Source

How to enrich the sessions you collect yourself

Run your own rtcstats-server and still get Observations and quality scores, using the stateless enrich endpoint or the standalone rtcstats-enrichment server.

Last updated Applies tortcstats-serverrtcstats-enrichment

On this page7 sections

Running on premise with rtcstats-server makes you in control of your data. When enrichment is needed, you can use rtcstats.com's enrich API, or you can install our standalone rtcstats-enrichment server alongside your rtcstats-server. This is a powerful setup for services in need of stricter governance and data ownership and for those operating at large scale and are looking to query enrichment related data at scale.

Out of the box, rtcstats-server gives you features in your SQL database, that you can already query across every session. Adding the enrichment layer, introduces also the Observations that flag what actually went wrong in a given call, and the Experience Score that says how that call felt in a single number.

This guide covers both ways to reach it: the stateless enrich endpoint on the rtcStats API, and the standalone rtcstats-enrichment server that runs the same engine inside your own network. Either way the outcome is the same one you were missing, which is a score and a set of Observations sitting next to the raw dump you already keep, for every session rather than only the ones somebody thought to investigate.

Two ways to get enrichment

Both paths run the same @rtcstats/rtcstats-processor engine and return the same payload shape, so whatever you write to parse one works unchanged against the other. What differs is where the dump is actually processed, and that single difference is usually what your security review cares about, so it is worth deciding deliberately rather than by whichever path you wired up first. The enrich endpoint, POST /v1.0/enrich, takes a dump, runs it through the analysis pipeline on rtcstats.com, and returns only the scores and Observations. The session is never stored. There is nothing to deploy, so this is the fastest way to see what enrichment gives you on real dumps you already have. The standalone rtcstats-enrichment server is a small REST service that runs the same engine next to your own stack, so no dump leaves your walls at all. Point your rtcstats-server at it, or call it directly.

A practical sequence is to start on the endpoint, decide from the real responses which fields you actually want in your database, and move to the standalone server when the binding requirement becomes that nothing leaves your infrastructure. Note that the standalone server is not part of the open-source stack you can clone from GitHub today. If that is the path you need, contact us and we will work out access with you.

Call the enrich endpoint

The endpoint is authenticated with a Bearer token and consumes one credit per successful response. For a small dump, send the file as the raw request body:

curl -X POST https://api.rtcstats.com/v1.0/enrich \
  -H "Authorization: Bearer $RTCSTATS_TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @session-dump.json

Both webrtc-internals JSON and the rtcstats event stream are accepted, plain or gzipped, and gzip is detected automatically so you do not need to declare it.

Larger dumps go through the same chunked protocol as upload. Post each chunk as multipart/form-data with chunk, fileId and chunkIndex fields, each returning { "success": true }, then send a JSON assemble request to run the whole thing through the pipeline:

{ "fileId": "your-file-id", "assemble": true }

Chunk requests verify that you have credits but consume none. The credit moves only once a response is actually produced, so a failed parse does not cost you one. If you would rather read working code than assemble the requests by hand, there is a reference implementation in the rtcStats API examples:

node script/api/enrich.mjs <token> [filePath] [baseUrl]

What comes back

The response is deliberately small and flat, shaped so you can write it into your own tables rather than reshape it first.

{
  "data": {
    "scores": {
      "experienceScore": 78.66,
      "audioScore": 4.1,
      "videoScore": 3.8,
      "connectivityScore": 4.5,
      "observationsScore": 32
    },
    "observationsCount": { "critical": 0, "high": 1, "medium": 1, "low": 2, "info": 0 },
    "observations": [
      {
        "type": "framesDropped",
        "severity": "medium",
        "category": "streams",
        "tags": ["video"],
        "valueInPercent": 4.2,
        "firstSeenAt": 1737000000000
      }
    ],
    "userAgentData": { "platform": "macOS", "mobile": false, "brands": [] }
  },
  "processorVersion": "1.9.0",
  "generatedAt": 1737000000123
}

Reading it field by field:

  • scores - the five analyzer scores. experienceScore is out of 100; audioScore, videoScore and connectivityScore are on the MOS scale; observationsScore reflects what the rules engine found. Any of them can be null when the session carries too little of that media to judge.
  • observationsCount - per-severity counts that sum to the length of observations. Store this if you want a cheap severity histogram without unpacking every record.
  • observations - one flat record per finding. type and severity are always present. Every other field (category, tags, firstSeenAt, url, property, valueInCount, valueInPercent, averageInMs, maxInMs, durationInMs, durationInSeconds, durationInPercent) appears only when it has a real value, never as null. That is what keeps the rows SQL-friendly.
  • userAgentData - browser, platform and hardware information taken from the dump, or {} when the dump carries none.
  • processorVersion - the @rtcstats/rtcstats-processor version that produced this analysis. Store it. When a score shifts between two sessions you believed were identical, this is the field that tells you whether the engine changed underneath you.
  • generatedAt - Unix epoch time in milliseconds for when the response was generated.

Wire it into your pipeline

The natural place to call enrich is the moment rtcstats-server finishes writing a dump. You already have the identifiers you configured at collection time, so join on those and you end up with a table where every row carries your own call ID, your own user ID, and the Experience Score for that session. That is where questions you could not previously answer collapse into one query. Which release regressed the Experience Score. Whether your users in one country score worse than another. Which Observation shows up most often in the sessions your support team escalates. The feature extraction your server performs describes what happened on the wire; the enrichment says what it meant for the person on the call. Held in the same database, the two halves are the full picture, and neither of them required handing your sessions to anyone else. Enriching on write rather than on demand also means the answer is already there the day someone asks a question you did not anticipate.

What enrichment does not include

The enrich response is the scores and Observations projection and nothing else. It carries no raw per-connection statistics, no aggregated stats, no AI Summary, and no session metadata. Those stay on rtcstats.com along with the interactive session viewer and the collaboration features. This is a deliberate split rather than a limitation to engineer around. You keep the raw dumps, because they are yours and they already sit in your storage. You get the derived judgment to use for analysis at scale. When you do want the full visual analysis on one particular session, upload that session and get the dashboard, the charts and the AI Summary for it, without that becoming the default for every call your platform handles. In practice that is a small fraction of your traffic.

Errors you should handle

  • authentication - the Bearer token is missing or invalid.
  • forbidden - the plan on this account does not include API access. API and MCP access are on the Developer plan and up.
  • no_credits - the account is out of credits. Uploads, analyze and enrich each cost one credit, and rtcStats blocks rather than quietly charging past your ceiling.
  • invalid_request - a malformed request, or an assemble request for a fileId the server does not know.
  • file_too_large - the dump is over the size limit.
  • parsing_issue and other_issue - the pipeline could not read the dump.

See also

Was this page helpful?