API Reference

The ProviderDownload API gives you real-time access to the full NPPES dataset — 7+ million NPI records with taxonomy, practice locations, and monthly change tracking.

For an interactive explorer, see the Swagger UI.

Authentication

All requests require an API key passed in the X-Api-Key header. Get a key by signing up.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1234567890 \
  -H "X-Api-Key: YOUR_API_KEY"
import requests

headers = {"X-Api-Key": "YOUR_API_KEY"}
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1234567890",
    headers=headers
)
print(r.json())
const res = await fetch("https://api.providerdownload.com/v1/providers/1234567890", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const data = await res.json();
console.log(data);
const https = require("https");

const options = {
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1234567890",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
};

https.get(options, res => {
  let body = "";
  res.on("data", chunk => body += chunk);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1234567890");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
print_r($data);
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var response = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1234567890");
Console.WriteLine(response);

Base URL

https://api.providerdownload.com

All endpoints are HTTPS only. Responses are JSON.

Errors

StatusMeaning
401Missing or invalid API key
404NPI not found
422Invalid request parameters
429Monthly quota exceeded
500Internal server error

Rate Limits

PlanMonthly requestsBulk NPIs per call
Free1,000100
Starter10,000500
Pro100,0001,000

Check your current usage at GET /account/me. When you're close to the limit, the response includes a quota_warning message.

Lookup by NPI

GET/v1/providers/{npi}

Returns full details for a single NPI number.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1003000118 \
  -H "X-Api-Key: YOUR_API_KEY"
import requests

headers = {"X-Api-Key": "YOUR_API_KEY"}
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1003000118",
    headers=headers
)
provider = r.json()
print(provider["first_name"], provider["last_name"])
const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const provider = await res.json();
console.log(provider.first_name, provider.last_name);
const https = require("https");

https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1003000118",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$npi = "1003000118";
$ch = curl_init("https://api.providerdownload.com/v1/providers/{$npi}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $data["first_name"] . " " . $data["last_name"];
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1003000118");
Console.WriteLine(json);
GET/v1/providers

Search and filter the full NPI registry. All parameters are optional.

ParameterTypeDescription
namestringLast name or organization name (partial match)
first_namestringFirst name (individuals only, partial match)
entity_typestringindividual or organization
statusstringactive or deactivated
statestringTwo-letter practice state (e.g. FL)
citystringPractice city
zipstringPractice ZIP code
taxonomystringExact NUCC taxonomy code
specialtystringSpecialty name search (partial match)
genderstringM or F
pageintegerPage number (default: 1)
limitintegerResults per page, max 200 (default: 20)
curl
Python
JavaScript
Node.js
PHP
C#
# Search by last name and state
curl "https://api.providerdownload.com/v1/providers?name=smith&state=FL&limit=10" \
  -H "X-Api-Key: YOUR_API_KEY"

# Search by first and last name
curl "https://api.providerdownload.com/v1/providers?name=smith&first_name=john&state=FL" \
  -H "X-Api-Key: YOUR_API_KEY"

# Search by specialty
curl "https://api.providerdownload.com/v1/providers?specialty=cardiology&state=FL&limit=10" \
  -H "X-Api-Key: YOUR_API_KEY"
import requests

r = requests.get(
    "https://api.providerdownload.com/v1/providers",
    headers={"X-Api-Key": "YOUR_API_KEY"},
    params={"name": "smith", "first_name": "john", "state": "FL", "limit": 10}
)
results = r.json()
for p in results["results"]:
    print(p["npi"], p.get("first_name"), p.get("last_name") or p.get("organization_name"))
const params = new URLSearchParams({ name: "smith", first_name: "john", state: "FL", limit: 10 });
const res = await fetch(`https://api.providerdownload.com/v1/providers?${params}`, {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const { results } = await res.json();
results.forEach(p => console.log(p.npi, p.first_name, p.last_name || p.organization_name));
const https = require("https");
const qs = require("querystring");

const query = qs.stringify({ specialty: "cardiology", state: "FL", limit: 10 });
https.get({
  hostname: "api.providerdownload.com",
  path: `/v1/providers?${query}`,
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$params = http_build_query(["specialty" => "cardiology", "state" => "FL", "limit" => 10]);
$ch = curl_init("https://api.providerdownload.com/v1/providers?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$results = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($results as $p) {
    echo $p["npi"] . "\n";
}
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var url = "https://api.providerdownload.com/v1/providers?specialty=cardiology&state=FL&limit=10";
var json = await client.GetStringAsync(url);
Console.WriteLine(json);

Bulk Lookup

POST/v1/bulk/lookup

Resolve up to 1,000 NPIs in a single request. Optionally embed sub-resources (taxonomy, locations, endpoints, names) in each result.

FieldTypeDescription
npis requiredstring[]Array of 10-digit NPI numbers
includestring[]Sub-resources to embed: taxonomy, locations, endpoints, names
curl
Python
JavaScript
Node.js
PHP
C#
curl -X POST https://api.providerdownload.com/v1/bulk/lookup \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"npis": ["1003000118", "1003001074"], "include": ["taxonomy"]}'
import requests

r = requests.post(
    "https://api.providerdownload.com/v1/bulk/lookup",
    headers={"X-Api-Key": "YOUR_API_KEY"},
    json={
        "npis": ["1003000118", "1003001074"],
        "include": ["taxonomy"]
    }
)
for provider in r.json():
    print(provider["npi"], provider.get("taxonomy"))
const res = await fetch("https://api.providerdownload.com/v1/bulk/lookup", {
  method: "POST",
  headers: {
    "X-Api-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    npis: ["1003000118", "1003001074"],
    include: ["taxonomy"]
  })
});
const results = await res.json();
results.forEach(p => console.log(p.npi, p.taxonomy));
const https = require("https");

const body = JSON.stringify({
  npis: ["1003000118", "1003001074"],
  include: ["taxonomy"]
});

const req = https.request({
  hostname: "api.providerdownload.com",
  path: "/v1/bulk/lookup",
  method: "POST",
  headers: {
    "X-Api-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Content-Length": Buffer.byteLength(body)
  }
}, res => {
  let data = "";
  res.on("data", c => data += c);
  res.on("end", () => console.log(JSON.parse(data)));
});
req.write(body);
req.end();
<?php
$payload = json_encode([
    "npis"    => ["1003000118", "1003001074"],
    "include" => ["taxonomy"]
]);
$ch = curl_init("https://api.providerdownload.com/v1/bulk/lookup");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-Api-Key: YOUR_API_KEY",
    "Content-Type: application/json"
]);
$results = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($results as $p) {
    echo $p["npi"] . "\n";
}
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");

var payload = new {
    npis    = new[] { "1003000118", "1003001074" },
    include = new[] { "taxonomy" }
};
var content = new StringContent(
    System.Text.Json.JsonSerializer.Serialize(payload),
    System.Text.Encoding.UTF8,
    "application/json"
);
var response = await client.PostAsync(
    "https://api.providerdownload.com/v1/bulk/lookup", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());

Taxonomy

GET/v1/providers/{npi}/taxonomy

Returns all taxonomy codes for a provider, including primary designation, license numbers, and NUCC descriptions.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1003000118/taxonomy \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1003000118/taxonomy",
    headers={"X-Api-Key": "YOUR_API_KEY"}
)
for code in r.json():
    print(code["code"], code["display_name"], "primary:", code["primary"])
const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/taxonomy", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const codes = await res.json();
codes.forEach(c => console.log(c.code, c.display_name));
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1003000118/taxonomy",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/taxonomy");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$codes = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($codes as $c) {
    echo $c["code"] . " " . $c["display_name"] . "\n";
}
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1003000118/taxonomy");
Console.WriteLine(json);

Practice Locations

GET/v1/providers/{npi}/locations

Returns secondary practice locations for a provider. The primary practice address is included in the main provider record.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1003000118/locations \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1003000118/locations",
    headers={"X-Api-Key": "YOUR_API_KEY"}
)
for loc in r.json():
    print(loc["address_line1"], loc["city"], loc["state"])
const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/locations", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const locations = await res.json();
locations.forEach(l => console.log(l.address_line1, l.city, l.state));
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1003000118/locations",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/locations");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$locs = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($locs as $l) {
    echo $l["address_line1"] . ", " . $l["city"] . "\n";
}
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1003000118/locations");
Console.WriteLine(json);

HIE Endpoints

GET/v1/providers/{npi}/endpoints

Returns Health Information Exchange (HIE) endpoints — Direct Messaging addresses, FHIR endpoints, SOAP URLs, and more.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1003000118/endpoints \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1003000118/endpoints",
    headers={"X-Api-Key": "YOUR_API_KEY"}
)
print(r.json())
const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/endpoints", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1003000118/endpoints",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/endpoints");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1003000118/endpoints");
Console.WriteLine(json);

Name History

GET/v1/providers/{npi}/names

Returns every name combination ever recorded for a provider, including legal names and other names (former names, maiden names, etc.).

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/v1/providers/1003000118/names \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/1003000118/names",
    headers={"X-Api-Key": "YOUR_API_KEY"}
)
print(r.json())
const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/names", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/1003000118/names",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/names");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/1003000118/names");
Console.WriteLine(json);

Recent Changes

GET/v1/providers/changes

The umbrella change feed. Returns all providers whose status, name, or record changed on or after since. The change_type field is one of: new (first seen), deactivated, reactivated, or updated. For more targeted pulls use /deactivated, /name-changes, or /updated.

Cursor pagination: For large result sets, use the next_cursor value from each response as the cursor= parameter on the next request. Cursor-based pagination is stable — it won't skip or duplicate rows if data updates between pages. Offset-based page= pagination is still supported but can drift on large result sets.
ParameterTypeDescription
sincedateRequired. Return changes on or after this date (YYYY-MM-DD)
cursorstringOpaque cursor from the previous page's next_cursor. Overrides page=.
pageintegerPage number (default: 1). Use cursor= for large result sets.
limitintegerResults per page, max 1000 (default: 100)
curl
Python
JavaScript
Node.js
PHP
C#
curl "https://api.providerdownload.com/v1/providers/changes?limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/changes",
    headers={"X-Api-Key": "YOUR_API_KEY"},
    params={"limit": 50}
)
print(r.json())
const res = await fetch("https://api.providerdownload.com/v1/providers/changes?limit=50", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/changes?limit=50",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/changes?limit=50");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/changes?limit=50");
Console.WriteLine(json);

Deactivated NPIs

GET/v1/providers/deactivated

Returns NPIs that were deactivated. Filter by since/until date range, state, entity type, or taxonomy. Supports cursor pagination via next_cursor / cursor=.

curl
Python
JavaScript
Node.js
PHP
C#
curl "https://api.providerdownload.com/v1/providers/deactivated?limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/v1/providers/deactivated",
    headers={"X-Api-Key": "YOUR_API_KEY"},
    params={"limit": 50}
)
print(r.json())
const res = await fetch("https://api.providerdownload.com/v1/providers/deactivated?limit=50", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());
https.get({
  hostname: "api.providerdownload.com",
  path: "/v1/providers/deactivated?limit=50",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => console.log(JSON.parse(body)));
});
<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/deactivated?limit=50");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/v1/providers/deactivated?limit=50");
Console.WriteLine(json);

Usage & Limits

GET/account/me

Returns your current plan, monthly usage, and remaining quota. When you reach 80% of your limit, a quota_warning message is included.

curl
Python
JavaScript
Node.js
PHP
C#
curl https://api.providerdownload.com/account/me \
  -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
    "https://api.providerdownload.com/account/me",
    headers={"X-Api-Key": "YOUR_API_KEY"}
)
account = r.json()
print(f"Plan: {account['plan']}")
print(f"Used: {account['monthly_used']} / {account['monthly_limit']}")
if account.get("quota_warning"):
    print(f"Warning: {account['quota_warning']}")
const res = await fetch("https://api.providerdownload.com/account/me", {
  headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const account = await res.json();
console.log(`Plan: ${account.plan}`);
console.log(`Used: ${account.monthly_used} / ${account.monthly_limit}`);
if (account.quota_warning) console.warn(account.quota_warning);
https.get({
  hostname: "api.providerdownload.com",
  path: "/account/me",
  headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
  let body = "";
  res.on("data", c => body += c);
  res.on("end", () => {
    const account = JSON.parse(body);
    console.log(`Plan: ${account.plan}, Used: ${account.monthly_used}`);
  });
});
<?php
$ch = curl_init("https://api.providerdownload.com/account/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$account = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Plan: " . $account["plan"] . "\n";
echo "Used: " . $account["monthly_used"] . " / " . $account["monthly_limit"] . "\n";
var json = await client.GetStringAsync(
    "https://api.providerdownload.com/account/me");
Console.WriteLine(json);

Forgot your API key?

If you've lost your API key, POST your email address and we'll rotate it and send the new key to your inbox. Always returns the same response regardless of whether the email exists, to prevent account enumeration.

POST/auth/resend-key
curl
Python
JavaScript
curl -X POST https://api.providerdownload.com/auth/resend-key \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'
import requests
r = requests.post("https://api.providerdownload.com/auth/resend-key",
                  json={"email": "you@example.com"})
print(r.json()["message"])
await fetch("https://api.providerdownload.com/auth/resend-key", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "you@example.com" }),
});

Response

{
  "message": "If that email has an account, a new API key has been sent."
}
Rate limited to 5 requests per hour per IP. The new key is sent via email; your previous key is immediately deactivated.

Rotate your API key

Immediately deactivates your current key and returns a new one. Use this if your key is compromised. Requires your existing key.

POST/account/rotate-key
curl
Python
JavaScript
curl -X POST https://api.providerdownload.com/account/rotate-key \
  -H "X-Api-Key: your_api_key"
import requests
r = requests.post("https://api.providerdownload.com/account/rotate-key",
                  headers={"X-Api-Key": "your_api_key"})
new_key = r.json()["api_key"]
print(new_key)
const res = await fetch("https://api.providerdownload.com/account/rotate-key", {
  method: "POST",
  headers: { "X-Api-Key": "your_api_key" },
});
const { api_key } = await res.json();
console.log("New key:", api_key);

Response

{
  "api_key": "nppes_...",
  "plan":    "free",
  "message": "Your previous key has been deactivated. Store this new key safely."
}

Billing Portal

Returns a one-time URL to the Stripe Customer Portal where paid subscribers can cancel, upgrade, downgrade, or update their payment method. Requires an active Starter or Pro API key.

POST/account/portal
curl
Python
JavaScript
curl -X POST https://api.providerdownload.com/account/portal \
  -H "X-Api-Key: your_api_key"
import requests, webbrowser
r = requests.post("https://api.providerdownload.com/account/portal",
                  headers={"X-Api-Key": "your_api_key"})
webbrowser.open(r.json()["url"])
const res = await fetch("https://api.providerdownload.com/account/portal", {
  method: "POST",
  headers: { "X-Api-Key": "your_api_key" },
});
const { url } = await res.json();
window.location.href = url;

Response

{
  "url": "https://billing.stripe.com/session/..."
}
The portal URL is single-use and expires after a short time. Redirect the user to it immediately — do not store or reuse it. When the user is done, Stripe returns them to providerdownload.com. Cancellations take effect at the end of the current billing period.

Webhooks

Webhooks let you receive a POST to your server when NPI data changes. Available on the Pro plan only.

Webhooks fire after each CMS NPPES data update — both the weekly incremental releases and the full monthly refresh. They are not real-time event streams.

Event types

EventDescription
npi.deactivatedNPI was deactivated in this data update
npi.name_changedProvider acquired a new legal name
npi.reactivatedPreviously deactivated NPI was reactivated
npi.address_changedPractice address (line1, line2, city, state, or ZIP) changed
npi.taxonomy_changedPrimary taxonomy code changed, or the set of non-primary codes changed (order-independent)

Filtering

All three filter fields are optional and OR'd together. An NPI fires if it matches any filter. Omit all filters to receive changes for all NPIs.

FieldTypeDescription
filter_npisstring[]List of 10-digit NPI numbers
filter_statesstring[]2-letter state codes (e.g. ["FL","TX"])
filter_zipsstring[]ZIP codes (e.g. ["32301","32303"])

Signature verification

Every request includes an X-Webhook-Signature header with a sha256= HMAC signature computed over the raw request body using your subscription secret. Always verify this before processing.

Python
JavaScript
PHP
C#
import hashlib, hmac

def verify(secret: str, body: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
const crypto = require("crypto");

function verify(secret, body, signature) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
<?php
function verifyWebhook(string $secret, string $body, string $signature): bool {
    $expected = "sha256=" . hash_hmac("sha256", $body, $secret);
    return hash_equals($expected, $signature);
}
using System.Security.Cryptography;
using System.Text;

bool Verify(string secret, byte[] body, string signature) {
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = "sha256=" + Convert.ToHexString(hmac.ComputeHash(body)).ToLower();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(hash),
        Encoding.UTF8.GetBytes(signature));
}

Payload format

Example
{
  "event":   "npi.deactivated",
  "period":  "2026-06",
  "sent_at": "2026-06-03T14:22:00Z",
  "npis": [
    {
      "npi":               "1234567890",
      "last_name":         "Smith",
      "first_name":        "John",
      "organization_name": null,
      "state":             "FL",
      "city":              "Miami",
      "zip":               "33101"
    }
  ]
}

Manage subscriptions

POST/v1/webhooks/subscriptions

Create a webhook. The secret in the response is shown once — store it immediately.

curl
Python
JavaScript
# Subscribe to all deactivations in Florida
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/nppes",
    "events": ["npi.deactivated", "npi.name_changed"],
    "filter_states": ["FL"]
  }'

# Subscribe to specific NPIs
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/nppes",
    "events": ["npi.deactivated"],
    "filter_npis": ["1234567890", "9876543210"]
  }'

# Subscribe to Tallahassee by ZIP code
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/nppes",
    "events": ["npi.deactivated", "npi.name_changed"],
    "filter_zips": ["32301","32303","32304","32308","32309","32312"]
  }'
import requests

r = requests.post(
    "https://api.providerdownload.com/v1/webhooks/subscriptions",
    headers={"X-Api-Key": "YOUR_API_KEY"},
    json={
        "url":           "https://your-server.com/webhooks/nppes",
        "events":        ["npi.deactivated", "npi.name_changed"],
        "filter_states": ["FL"],
    }
)
sub = r.json()
print("ID:",     sub["id"])
print("Secret:", sub["secret"])  # store this — shown once
const res = await fetch("https://api.providerdownload.com/v1/webhooks/subscriptions", {
  method: "POST",
  headers: { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({
    url:           "https://your-server.com/webhooks/nppes",
    events:        ["npi.deactivated", "npi.name_changed"],
    filter_states: ["FL"],
  })
});
const sub = await res.json();
console.log("Secret:", sub.secret); // store this — shown once
GET/v1/webhooks/subscriptions

List your active webhook subscriptions. The secret is not returned here.

GET/v1/webhooks/subscriptions/{id}/deliveries

Returns the last 100 delivery attempts for a subscription — status, HTTP response code, attempt count, and timestamp.

DELETE/v1/webhooks/subscriptions/{id}

Delete a webhook subscription.