← Back to Benchmark Data

Benchmark API Documentation

Programmatic access to physician compensation benchmark data

Base URL: https://www.salarydr.com/api/v1Auth: X-API-Key header

Quick Start

All API requests require an API key passed via the X-API-Key header. Your key is provided after subscribing to an API plan.

# 1. Check which specialties have data
curl -H "X-API-Key: sdr_live_your_key_here" \
  "https://www.salarydr.com/api/v1/specialties"

# 2. Check data availability for your query
curl -H "X-API-Key: sdr_live_your_key_here" \
  "https://www.salarydr.com/api/v1/available-data?specialty=Family+Medicine&state=New+York"

# 3. Retrieve full benchmark data
curl -H "X-API-Key: sdr_live_your_key_here" \
  "https://www.salarydr.com/api/v1/benchmarks?specialty=Family+Medicine&state=New+York"

# 4. Check your usage and remaining limits
curl -H "X-API-Key: sdr_live_your_key_here" \
  "https://www.salarydr.com/api/v1/me/usage"

Endpoints

GET/specialties

List all specialties with available data counts and geographic coverage. Useful for populating dropdowns or validating inputs. Cached for 24 hours.

Example Request

curl -H "X-API-Key: sdr_live_abc123..." \
  "https://www.salarydr.com/api/v1/specialties"

Example Response

{
  "specialties": [
    {
      "name": "Family Medicine",
      "sample_size": 142,
      "states_with_data": 28,
      "publishable": true,
      "sufficient": true
    },
    {
      "name": "Cardiology",
      "sample_size": 67,
      "states_with_data": 18,
      "publishable": true,
      "sufficient": true
    }
  ],
  "count": 35
}
GET/available-data

Preview data availability before querying benchmarks. Returns sample sizes at each geographic scope, available years, and BLS data availability. Cached for 1 hour.

Query Parameters

ParameterRequiredDescription
specialtyRequiredPhysician specialty (e.g., "Family Medicine")
stateOptionalUS state name (e.g., "New York")

Example Request

curl -H "X-API-Key: sdr_live_abc123..." \
  "https://www.salarydr.com/api/v1/available-data?specialty=Family+Medicine&state=New+York"

Example Response

{
  "specialty": "Family Medicine",
  "availability": {
    "state": {
      "label": "New York",
      "n": 12,
      "years": [2023, 2024, 2025, 2026],
      "sufficient": false
    },
    "regional": {
      "label": "Northeast",
      "n": 38,
      "years": [2022, 2023, 2024, 2025, 2026],
      "sufficient": true
    },
    "national": {
      "label": "National",
      "n": 142,
      "years": [2021, 2022, 2023, 2024, 2025, 2026],
      "sufficient": true
    }
  },
  "bls_available": true,
  "bls_soc_code": "29-1215"
}
GET/benchmarks

Retrieve full compensation percentile benchmarks with multi-scope data, statistical confidence, and optional breakdowns. Primary data endpoint.

Query Parameters

ParameterRequiredDescription
specialtyRequiredPhysician specialty (e.g., "Family Medicine", "Cardiology")
stateOptionalUS state (e.g., "New York", "California")
regionOptionalUS region: Northeast, Midwest, South, West
year_startOptionalFilter start year (e.g., 2024)
year_endOptionalFilter end year (e.g., 2026)
practice_settingOptionalHospital Employed, Private Practice, Academic, etc.
employment_typeOptionalFull-Time (W-2), Independent Contractor (1099), etc.

Example Request

curl -H "X-API-Key: sdr_live_abc123..." \
  "https://www.salarydr.com/api/v1/benchmarks?specialty=Family+Medicine&state=New+York"

Example Response

{
  "data": {
    "specialty": "Family Medicine",
    "geography": {
      "state": "New York",
      "region": "Northeast",
      "scope_used": "state"
    },
    "sample_size": 12,
    "percentiles": {
      "p10": 195000,
      "p25": 220000,
      "p50": 285000,
      "p75": 340000,
      "p90": 395000
    },
    "mean": 298000,
    "confidence": {
      "sufficient": false,
      "publishable": true,
      "standard_deviation": 62000,
      "confidence_interval_95": {
        "lower": 249900,
        "upper": 320100
      }
    },
    "scopes": {
      "state": {
        "label": "New York",
        "n": 12,
        "publishable": true,
        "sufficient": false,
        "percentiles": { "p10": 195000, "p25": 220000, "p50": 285000, "p75": 340000, "p90": 395000 },
        "mean": 298000
      },
      "regional": {
        "label": "Northeast",
        "n": 38,
        "publishable": true,
        "sufficient": true,
        "percentiles": { "p10": 190000, "p25": 215000, "p50": 278000, "p75": 335000, "p90": 390000 },
        "mean": 290000
      },
      "national": {
        "label": "National",
        "n": 142,
        "publishable": true,
        "sufficient": true,
        "percentiles": { "p10": 180000, "p25": 210000, "p50": 270000, "p75": 330000, "p90": 385000 },
        "mean": 282000
      }
    },
    "bls_comparison": {
      "available": true,
      "bls_data": {
        "soc_code": "29-1215",
        "soc_title": "Family Medicine Physicians",
        "median": 224460,
        "year": 2024
      },
      "salarydr_median": 285000,
      "variance_pct": 26.9,
      "methodology_note": "BLS OEWS uses employer-reported W-2 wages; SalaryDr includes total compensation."
    },
    "methodology": {
      "min_sample": 5,
      "outlier_handling": "Winsorized at $0 and $2,000,000",
      "fte_basis": "40 hours/week (2,080 hours/year)",
      "data_date_range": {
        "earliest": "2023-06-15",
        "latest": "2026-02-28"
      }
    },
    "data_as_of": "2026-03-03"
  },
  "meta": {
    "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "rate_limit": {
      "remaining_daily": 99,
      "remaining_monthly": 2999,
      "reset": 1741219200
    }
  }
}

reset is a Unix epoch timestamp (seconds) for the next daily limit reset at midnight UTC.

Professional/Enterprise: Additional Fields

Higher tiers include FTE-normalized data, trending, breakdowns, and compensation components:

// Additional fields in the response for Professional/Enterprise tiers:
{
  "data": {
    ...
    "fte_normalized": {
      "percentiles": { "p10": 200000, "p25": 228000, "p50": 290000, "p75": 345000, "p90": 400000 },
      "mean": 302000,
      "n": 12
    },
    "components": {
      "base_salary": { "p25": 200000, "p50": 250000, "p75": 300000 },
      "bonus": { "p25": 15000, "p50": 35000, "p75": 55000 }
    },
    "trending": [
      { "year": 2024, "n": 8, "p25": 210000, "p50": 265000, "p75": 320000, "mean": 275000, "publishable": true },
      { "year": 2025, "n": 12, "p25": 220000, "p50": 285000, "p75": 340000, "mean": 298000, "publishable": true }
    ],
    "breakdowns": {
      "by_experience": [
        { "label": "0-5 years", "n": 6, "p25": 195000, "p50": 240000, "p75": 280000, "mean": 245000, "publishable": true }
      ],
      "by_practice_setting": [...],
      "by_employment_type": [...]
    }
  }
}

JavaScript Example

const response = await fetch(
  'https://www.salarydr.com/api/v1/benchmarks?specialty=Family+Medicine&state=New+York',
  { headers: { 'X-API-Key': 'sdr_live_your_key_here' } }
);

const { data, meta } = await response.json();

// Primary benchmark data
console.log(`Median: $${data.percentiles.p50.toLocaleString()}`);
console.log(`Sample: n=${data.sample_size}`);
console.log(`95% CI: $${data.confidence.confidence_interval_95.lower.toLocaleString()} – $${data.confidence.confidence_interval_95.upper.toLocaleString()}`);

// Compare scopes
if (data.scopes.state) {
  console.log(`State median: $${data.scopes.state.percentiles.p50.toLocaleString()} (n=${data.scopes.state.n})`);
}
console.log(`National median: $${data.scopes.national.percentiles.p50.toLocaleString()} (n=${data.scopes.national.n})`);

// Rate limit tracking
console.log(`Remaining requests today: ${meta.rate_limit.remaining_daily}`);

Python Example

import requests

response = requests.get(
    "https://www.salarydr.com/api/v1/benchmarks",
    params={"specialty": "Family Medicine", "state": "New York"},
    headers={"X-API-Key": "sdr_live_your_key_here"},
)

data = response.json()["data"]
median = data["percentiles"]["p50"]
n = data["sample_size"]
ci = data["confidence"]["confidence_interval_95"]

print(f"Median: ${median:,}")
print(f"Sample: n={n}")
print(f"95% CI: ${ci['lower']:,} to ${ci['upper']:,}")

# Compare all geographic scopes
for scope in ["state", "regional", "national"]:
    s = data["scopes"].get(scope)
    if s:
        p50 = s["percentiles"]["p50"]
        print(f"  {s['label']}: ${p50:,} (n={s['n']})")
GET/me/usage

Check your API key's plan details, current usage, and remaining request limits. Does not count toward rate limits.

Example Request

curl -H "X-API-Key: sdr_live_abc123..." \
  "https://www.salarydr.com/api/v1/me/usage"

Example Response

{
  "plan_tier": "starter",
  "subscription_status": "active",
  "key_prefix": "sdr_live_abc",
  "created_at": "2026-03-01T10:00:00Z",
  "usage": {
    "requests_today": 15,
    "requests_this_month": 420,
    "daily_limit": 100,
    "monthly_limit": 3000,
    "remaining_daily": 85,
    "remaining_monthly": 2580,
    "daily_reset": 1741219200
  },
  "features": {
    "trending": false,
    "fte_normalized": false,
    "breakdowns": false
  }
}

daily_reset is a Unix epoch timestamp for the next midnight UTC reset.

Response Headers

Rate limit information is included in every response as both headers and the meta.rate_limit object.

HeaderDescription
X-RateLimit-Limit-DailyYour daily request limit
X-RateLimit-Remaining-DailyRemaining daily requests
X-RateLimit-Limit-MonthlyYour monthly request limit
X-RateLimit-Remaining-MonthlyRemaining monthly requests
X-RateLimit-ResetUnix epoch (seconds) — next daily reset at midnight UTC

Plans & Rate Limits

TierPriceDailyMonthlyFeatures
Starter$999/mo1003,000Percentiles, mean, multi-scope, BLS comparison
Professional$2,499/mo50015,000+ FTE, trending, breakdowns, components
EnterpriseCustom2,00060,000+ custom date ranges, priority support, SLA

Tier Feature Details

All Tiers

  • Percentile data: P10, P25, P50 (Median), P75, P90
  • Mean compensation
  • Sample size and data quality indicators
  • Multi-scope data: state, regional, and national in one response
  • Statistical confidence: standard deviation, 95% confidence interval
  • BLS OEWS cross-reference comparison
  • Practice setting and employment type filters
  • Data date range in methodology

Professional & Enterprise

  • FTE Normalized: Part-time compensation scaled to 40hr/week equivalent
  • Trending: Year-over-year percentile data with per-year sample sizes
  • Breakdowns: By experience level, practice setting, and employment type
  • Components: Base salary + bonus/incentive percentiles separately

Enterprise Only

  • Custom Date Ranges: Filter data by submission year range
  • Priority Support: Dedicated support channel and SLA
  • Higher Limits: 2,000 daily / 60,000 monthly requests

Data Quality Indicators

Every response includes quality indicators to help you assess data reliability:

FieldDescription
confidence.publishablen ≥ 5 — minimum for reporting
confidence.sufficientn ≥ 15 — high-confidence threshold
confidence.standard_deviationCompensation variance in the sample
confidence.confidence_interval_9595% CI around the median (lower/upper bounds)
scopes.*.publishablePer-scope data quality flag

Error Codes

CodeMeaningSolution
400Missing or invalid parametersCheck required params; use full state names (e.g., "New York")
401Invalid or missing API keyCheck your X-API-Key header
403Subscription inactive or feature not in tierUpdate payment or upgrade tier
404Insufficient data for the queryTry broader geography or use /available-data first
429Rate limit exceededWait until reset (see X-RateLimit-Reset header) or upgrade tier
503Service unavailableRetry after a brief delay

All error responses return {"error": "Description of the issue"}.

Best Practices

  • 1. Check availability first. Call /available-data before /benchmarks to avoid 404s on low-data queries.
  • 2. Use multi-scope data. The scopes object returns state, regional, and national data in a single request — no need for multiple calls.
  • 3. Cache responses client-side. Benchmark data is cached for 1 hour server-side. Respect the Cache-Control header.
  • 4. Check confidence indicators. Use confidence.sufficient (n≥15) for high-stakes decisions. publishable (n≥5) is the minimum threshold.
  • 5. Monitor rate limits. Track X-RateLimit-Remaining-Daily headers to avoid hitting limits.
  • 6. Handle 404 gracefully. Broaden your geographic scope (remove state filter) or check a different specialty.

Need Help?

Contact us for custom integrations, enterprise support, or questions about the API.