CallMeter logoCallMeter Docs

API Reference

Complete REST API endpoint reference for CallMeter, including authentication, resource operations, request/response formats, pagination, and rate limits.

The CallMeter REST API provides programmatic access to projects, tests, test runs, probes, and registrars. Use the API to automate test execution, integrate with CI/CD pipelines, build custom dashboards, and export data.

API in Active Development

The API reference is being expanded. The endpoints documented below represent the current stable API surface. Contact support@callmeter.io for additional endpoint documentation or to request new API capabilities.

Base URL

All API requests use the following base URL:

https://callmeter.io/api/v1

Authentication

Authenticate every request by including your API key in the Authorization header using the Bearer scheme:

curl -H "Authorization: Bearer cmk_your_api_key_here" \
  https://callmeter.io/api/v1/projects

API keys use the cmk_ prefix followed by 32 hexadecimal characters. Generate keys from API Keys in the project sidebar. See API Authentication for detailed instructions on key creation, permissions, and security best practices.

Keep Your API Key Secret

API keys grant full access according to the creating user's permissions. Never commit API keys to version control, expose them in client-side code, or share them in unsecured channels. Use environment variables or a secrets manager.

Request Format

  • Content-Type: application/json for request bodies
  • Accept: application/json for all responses
  • Method: Standard HTTP methods (GET, POST)
  • URL parameters: Path parameters use {param} notation (e.g., /projects/{projectId})
  • Query parameters: Used for filtering, pagination, and sorting

Response Format

All responses return JSON wrapped in a consistent envelope with data and meta fields.

Success response (single resource):

{
  "data": {
    "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "name": "Production Stress Test",
    "status": "COMPLETED",
    "createdAt": "2026-01-15T10:30:00Z"
  },
  "meta": {
    "requestId": "uuid"
  }
}

Success response (list):

{
  "data": [
    { "id": "abc123", "name": "Production Stress Test" },
    { "id": "def456", "name": "Staging Quality Check" }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": {
      "page": 1,
      "perPage": 25,
      "total": 42,
      "totalPages": 2
    }
  }
}

Error response:

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired API key"
  },
  "meta": {
    "requestId": "uuid"
  }
}

HTTP Status Codes

CodeMeaning
200Success
201Created (resource created successfully)
400Bad Request (invalid parameters)
401Unauthorized (missing or invalid API key)
403Forbidden (insufficient permissions)
404Not Found (resource does not exist)
422Unprocessable Entity (validation error)
429Too Many Requests (rate limit exceeded)
500Internal Server Error

Pagination

List endpoints use offset-based pagination.

Query parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
per_pageinteger25Number of items per page (max 100)

Example:

# First page
curl -H "Authorization: Bearer cmk_..." \
  "https://callmeter.io/api/v1/projects?page=1&per_page=10"

# Second page
curl -H "Authorization: Bearer cmk_..." \
  "https://callmeter.io/api/v1/projects?page=2&per_page=10"

When pagination.page equals pagination.totalPages, there are no more results.

Rate Limits

API requests are rate-limited per API key. When the rate limit is exceeded, the API returns a 429 Too Many Requests response with a Retry-After header indicating when to retry.

Rate limits are applied per API key. The limits that apply to your own organization are not published here -- ask us if you need them for capacity planning.


Projects

Projects are workspaces that group related tests, registrars, media files, and probes.

List Projects

List all projects accessible to the authenticated user.

GET /projects

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "slug": "production",
      "name": "Production",
      "description": "Production SIP infrastructure testing",
      "testCount": 12,
      "probeCount": 3,
      "registrarCount": 2
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

Get Project

Retrieve a project by ID.

GET /projects/{projectId}

Response: 200 OK with full project details including test count, probe count, and registrar count.


Tests

Tests represent reusable SIP testing scenario configurations. Each test can be run multiple times, producing independent test runs. All test endpoints are scoped under a project.

List Tests

List all tests in a project.

GET /projects/{projectId}/tests

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "abc123",
      "name": "Production Stress Test",
      "endpoints": 500,
      "durationSeconds": 120,
      "buildup": 30,
      "lastRunStatus": "COMPLETED",
      "createdAt": "2026-01-15T10:30:00Z",
      "updatedAt": "2026-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

Get Test

Retrieve a single test configuration by ID.

GET /projects/{projectId}/tests/{testId}

Response: 200 OK with full test configuration including groups, codecs, and media settings.

Run Test

Start a new execution of a test. Creates a test run and begins the allocation and execution process.

POST /projects/{projectId}/tests/{testId}/run

Response: 201 Created

{
  "data": {
    "id": "xyz789",
    "testId": "abc123",
    "status": "PENDING",
    "createdAt": "2026-01-15T11:00:00Z"
  },
  "meta": {
    "requestId": "uuid"
  }
}

The run transitions through PENDING, QUEUED, RUNNING, and then to one of the terminal states COMPLETED, FAILED, CANCELLED or CANNOT_RUN_FOR_NOW.


Test Runs

Test runs represent individual executions of a test. Each run has its own status, endpoints, and metrics. All test run endpoints are scoped under a project.

List Test Runs

List test runs in a project.

GET /projects/{projectId}/test-runs

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "xyz789",
      "status": "COMPLETED",
      "error": null,
      "priority": 0,
      "testId": "abc123",
      "testName": "Nightly trunk check",
      "endpoints": 500,
      "submittedAt": "2026-01-15T11:00:00Z",
      "startedAt": "2026-01-15T11:00:01Z",
      "endedAt": "2026-01-15T11:02:35Z"
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

Get Test Run

Retrieve a test run by ID, including status and summary statistics.

GET /projects/{projectId}/test-runs/{testRunId}

Response: 200 OK

{
  "data": {
    "id": "xyz789",
    "status": "COMPLETED",
    "error": null,
    "priority": 0,
    "isProbeRun": false,
    "testId": "abc123",
    "testName": "Nightly trunk check",
    "projectName": "Wholesale routes",
    "organizationName": "Acme Telecom",
    "endpoints": 500,
    "durationSeconds": 120,
    "submittedAt": "2026-01-15T11:00:00Z",
    "startedAt": "2026-01-15T11:00:01Z",
    "endedAt": "2026-01-15T11:02:35Z",
    "summary": {
      "avgMos": 4.21,
      "minMos": 3.84,
      "avgJitter": 8.3,
      "maxJitter": 24.1,
      "avgPacketLossFraction": 0.0012,
      "totalPacketsLost": 431,
      "avgRtt": 45.2,
      "maxRtt": 98.7,
      "avgRFactor": 89.4,
      "minRFactor": 71.2,
      "totalNackCount": 12,
      "totalPliCount": 3,
      "totalFirCount": 0,
      "totalPlcEvents": 27,
      "totalStreams": 996,
      "streamsWithFeedback": 994
    },
    "callTiming": {
      "totalCalls": 500,
      "successCount": 486,
      "timeoutCount": 4,
      "rejectedCount": 8,
      "errorCount": 2,
      "inProgressCount": 0,
      "asr": 97.2,
      "ner": 98.8,
      "pdd": { "p50": 812, "p95": 1640, "p99": 2210, "avg": 903.4, "min": 410, "max": 2380 },
      "timeToTrying": { "p50": 42, "p95": 88, "p99": 140, "avg": 51.2, "min": 18, "max": 210 },
      "timeToRinging": { "p50": 640, "p95": 1180, "p99": 1520, "avg": 702.5, "min": 310, "max": 1740 },
      "timeToFirstMedia": { "p50": 180, "p95": 340, "p99": 480, "avg": 201.7, "min": 95, "max": 610 },
      "callSetup": { "p50": 890, "p95": 1720, "p99": 2290, "avg": 981.3, "min": 470, "max": 2460 },
      "callDuration": { "p50": 60000, "p95": 60200, "p99": 60400, "avg": 59980, "min": 12000, "max": 60500 },
      "registerRtt": { "p50": 88, "p95": 165, "p99": 240, "avg": 96.4, "min": 41, "max": 310 }
    },
    "sipResponses": [
      { "statusCode": 200, "reasonPhrase": "OK", "count": 486, "percentage": 97.99 },
      { "statusCode": 503, "reasonPhrase": "Service Unavailable", "count": 6, "percentage": 1.21 },
      { "statusCode": 486, "reasonPhrase": "Busy Here", "count": 4, "percentage": 0.81 }
    ],
    "subPopulationInstances": [
      {
        "id": "spi-001",
        "endpoints": 500,
        "status": "ALLOCATED",
        "subPopulationId": "sp-001",
        "worker": { "id": "w-001", "name": "eu-west-1", "ownership": "USER_OWNED" }
      }
    ],
    "counts": {
      "events": 1042,
      "sipMessages": 5980
    }
  },
  "meta": {
    "requestId": "uuid"
  }
}

List Test Run Endpoints

List endpoints in a test run with their final status and per-endpoint call timing. Use this to find which route degraded when a run-level gate fails.

GET /projects/{projectId}/test-runs/{testRunId}/endpoints

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "ep_001",
      "endpointType": "CALLER",
      "phase": "CLOSED",
      "outcome": "SUCCESS",
      "calleeUri": "sip:user002@sip.example.com",
      "worker": { "id": "w-001", "name": "eu-west-1", "ownership": "USER_OWNED" },
      "registrar": { "id": "r-001", "name": "Primary SBC", "uri": "sip:sbc.example.com" },
      "callTiming": {
        "timeToTryingMs": 42,
        "timeToRingingMs": 640,
        "pddMs": 812,
        "callSetupMs": 890,
        "timeToFirstMediaMs": 180,
        "callDurationMs": 60000,
        "registerRttMs": 88,
        "finalStatusCode": 200,
        "finalReasonPhrase": "OK",
        "callResult": "SUCCESS"
      }
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 500, "totalPages": 20 }
  }
}

callTiming is null for an endpoint that never reached the timing collector — a call that failed at registration, for instance. That is not the same as timings of zero, so it is not zero-filled.


Probes

Probes are scheduled, continuous monitoring tests. They execute at regular intervals and evaluate quality thresholds.

List Probes

List all probes in a project.

GET /projects/{projectId}/probes

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "probe_abc123",
      "name": "Production Health Check",
      "description": "Hourly check on the primary interconnect",
      "mode": "CALL",
      "status": "HEALTHY",
      "enabled": true,
      "scheduleType": "FIXED_INTERVAL",
      "fixedIntervalSec": 900,
      "cronExpression": null,
      "timeOfDayStartUtc": null,
      "timeOfDayEndUtc": null,
      "timezone": "UTC",
      "durationSeconds": 30,
      "alertStrikeRuns": 2,
      "parentProbeId": null,
      "lastInstanceStatus": "COMPLETED",
      "lastInstanceAt": "2026-01-15T11:00:00Z",
      "escalateAfterMinutes": 60,
      "autoPauseAfterHours": null,
      "autoPausedAt": null,
      "autoPauseReason": null,
      "lastSkipReason": null,
      "lastSkipAt": null,
      "thresholds": [
        { "id": "th-001", "metricKey": "mos", "greenValue": 4.0, "orangeValue": 3.6, "enabled": true }
      ],
      "createdAt": "2026-01-15T09:00:00Z",
      "updatedAt": "2026-01-15T11:00:00Z"
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

Get Probe

Retrieve a probe by ID, including current health status, configuration, and threshold settings.

GET /projects/{projectId}/probes/{probeId}

Response: 200 OK with full probe configuration, current health status, last execution timestamp, and threshold definitions.

Reading a probe's operational state

status and enabled describe how a probe's last run went and whether it is switched on. Neither tells you whether it is still executing, and a probe can be enabled: true, status: "HEALTHY", and not have run for days. Two field pairs answer that:

FieldMeaning when non-null
lastSkipReason / lastSkipAtThe most recent scheduled run was refused before dispatch, typically by an organization usage or plan limit. Cleared automatically on the next successful run.
autoPausedAt / autoPauseReasonAn incident stayed open past autoPauseAfterHours, so the probe was disabled automatically. enabled is false in this state.

If you are building a monitoring integration, check lastSkipReason before trusting status. A non-null value means the probe stopped running while its last recorded status stayed exactly as it was.

List Probe Incidents

Incident history for a probe, and for its multi-region sibling probes. An incident opens once a probe has failed its configured number of consecutive runs (alertStrikeRuns) and closes when the probe recovers.

GET /projects/{projectId}/probes/{probeId}/incidents

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)
severitystringNoDEGRADED or DOWN
statestringNoopen for incidents still in progress, resolved for ended ones. Omit for both.

Results are ordered with open incidents first, then most recently started first.

Response: 200 OK

{
  "data": [
    {
      "id": "3f8a1c22-9c1e-4f7a-8b2e-11a2b3c4d5e6",
      "probeId": "9b2c4d61-77aa-4c3d-9f10-0a1b2c3d4e5f",
      "region": "eu-west",
      "severity": "DOWN",
      "startedAt": "2026-01-15T02:14:00Z",
      "endedAt": "2026-01-15T02:51:00Z",
      "durationMs": 2220000,
      "message": null
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

`message` is always null today

The field is published because it exists on the incident record, but nothing in the platform writes it yet — incidents are opened with a severity and a start time and no text. Treat it as reserved, and do not build alerting logic that depends on it being non-null.

endedAt and durationMs are null while an incident is still open — use ?state=open to list only those. region is derived from a trailing (region) suffix on the probe's name and is null for a probe that carries none.

List Probe Runs

Execution history for a probe, most recently scheduled first, with the per-metric threshold verdicts that produced each run's status.

GET /projects/{projectId}/probes/{probeId}/instances

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)
statusstringNoHEALTHY, DEGRADED, UNHEALTHY or UNKNOWN

Response: 200 OK

{
  "data": [
    {
      "id": "7c1d9e33-4b2a-4e88-9d31-2f4c6a8b0d12",
      "status": "DEGRADED",
      "testRunId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "scheduledAt": "2026-01-15T11:00:00Z",
      "startedAt": "2026-01-15T11:00:02Z",
      "completedAt": "2026-01-15T11:00:32Z",
      "thresholdResults": [
        {
          "metricKey": "mos",
          "displayName": "MOS",
          "unit": "",
          "value": 3.42,
          "status": "DEGRADED",
          "higherIsBetter": true
        }
      ]
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

thresholdResults is an empty array for a run that never completed and was therefore never graded. testRunId links to the test run this probe execution produced, so GET /projects/{projectId}/test-runs/{testRunId}/metrics expands any run into its full quality detail.

Run Probe

Trigger an immediate execution of a probe, outside its regular schedule.

POST /projects/{projectId}/probes/{probeId}/run

Response: 201 Created with the queued probe execution details.


Registrars

Registrars represent SIP server configurations used by tests and probes.

List Registrars

List all registrars configured for a project.

GET /projects/{projectId}/registrars

Query parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page (default 25, max 100)

Response: 200 OK

{
  "data": [
    {
      "id": "reg_abc123",
      "name": "Production SBC",
      "uri": "sip:pbx.example.com",
      "protocol": "UDP",
      "dns": false,
      "outboundProxyUri": null,
      "isCloud": false,
      "sipAccountCount": 500,
      "createdAt": "2026-01-15T09:00:00Z",
      "updatedAt": "2026-01-15T09:00:00Z"
    }
  ],
  "meta": {
    "requestId": "uuid",
    "pagination": { "page": 1, "perPage": 25, "total": 1, "totalPages": 1 }
  }
}

Error Handling

All error responses follow a consistent format:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid page parameter"
  },
  "meta": {
    "requestId": "uuid"
  }
}

Common Error Codes

CodeHTTP StatusDescription
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403Insufficient permissions for this operation
NOT_FOUND404Resource does not exist or is not accessible
VALIDATION_ERROR422Request body failed validation
RATE_LIMITED429Rate limit exceeded. Check Retry-After header.
INTERNAL_ERROR500Unexpected server error. Contact support if persistent.

CI/CD Integration Pattern

A common pattern for integrating CallMeter into deployment pipelines:

# 1. Trigger a test run
RUN_ID=$(curl -s -X POST \
  -H "Authorization: Bearer $CALLMETER_API_KEY" \
  "https://callmeter.io/api/v1/projects/$PROJECT_ID/tests/$TEST_ID/run" \
  | jq -r '.data.testRunId')

# 2. Poll until the run reaches a terminal state.
# COMPLETED is the only one worth evaluating. FAILED, CANCELLED and CANNOT_RUN_FOR_NOW are
# terminal too -- a loop that waits for them to become COMPLETED polls until its timeout and
# then reports a stall rather than what actually happened.
while true; do
  STATUS=$(curl -s \
    -H "Authorization: Bearer $CALLMETER_API_KEY" \
    "https://callmeter.io/api/v1/projects/$PROJECT_ID/test-runs/$RUN_ID" \
    | jq -r '.data.status')

  case "$STATUS" in
    COMPLETED) break ;;
    FAILED|CANCELLED|CANNOT_RUN_FOR_NOW)
      echo "Test ended with status: $STATUS"
      exit 1
      ;;
    *) sleep 10 ;;
  esac
done

# 3. Check quality gate
RUN=$(curl -s \
  -H "Authorization: Bearer $CALLMETER_API_KEY" \
  "https://callmeter.io/api/v1/projects/$PROJECT_ID/test-runs/$RUN_ID")

# A run that placed no calls has nothing to judge, and avgMos would read 0 -- which fails for
# the wrong reason and sends you to debug audio quality on a trunk that was never dialled.
TOTAL_CALLS=$(echo "$RUN" | jq -r '.data.callTiming.totalCalls')
if [ "$TOTAL_CALLS" -eq 0 ]; then
  echo "Quality gate FAILED: the run placed no calls - check registration"
  exit 1
fi

MOS=$(echo "$RUN" | jq -r '.data.summary.avgMos')

# awk, not bc. bc is absent from alpine, debian-slim and the official node images, and
# `(( $(... | bc -l) ))` with bc missing evaluates an EMPTY expression, which is false -- so the
# gate reports PASS on every run, including the bad ones.
if awk -v a="$MOS" -v b=3.5 'BEGIN { exit !(a < b) }'; then
  echo "Quality gate FAILED: MOS $MOS is below 3.5"
  exit 1
fi

echo "Quality gate PASSED: MOS $MOS"

See CI/CD Integration for complete pipeline examples.

On this page