ax-check.com
by Gauge
A
DoorProfit API
api.doorprofit.com
ax-check.com
Checked 18 October 2026
F
23/100
Agents need your login
Pricing is clear, but docs leave agents guessing
Billing page lists Free/Starter $25/Growth $50/Pro $100 with limits, no login needed. But no docs index, quickstart, or API reference was found for setup.
3 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no api.doorprofit.com account. Recorded 18 October 2026 on Pi.
3 recorded · 5m 32s
5m 32s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
I don't know DoorProfit. Let me try searching for it in the repository/environment.
ls -la /sandbox/repo 2>/dev/null; echo "---"; cat /sandbox/repo/README* 2>/dev/null | head -50
total 16
total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 18 02:39 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore ---
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-10-18T02:39:30.711Z",
"name": "bash",
"turn": 1,
"input": {
"command": "ls -la /sandbox/repo 2>/dev/null; echo \"---\"; cat /sandbox/repo/README* 2>/dev/null | head -50"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-98e09e9ae9195e04",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 5,
"ts": "2026-10-18T02:39:30.711Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 18 02:39 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n---\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-98e09e9ae9195e04",
"is_error": false,
"tool_kind": "shell"
}grep -ril "doorprofit" /sandbox /usr/local/lib/node_modules/@earendil-works 2>/dev/null | head; echo "---env---"; env | grep -i door
Failed/sandbox/.gauge/pi/sessions/2026-10-18T02-39-28-039Z_01a14ce0-df27-730b-84b7-1bb5e5d8a41e.jsonl
/sandbox/.gauge/pi/sessions/2026-10-18T02-39-28-039Z_01a14ce0-df27-730b-84b7-1bb5e5d8a41e.jsonl ---env--- Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-18T02:39:30.711Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -ril \"doorprofit\" /sandbox /usr/local/lib/node_modules/@earendil-works 2>/dev/null | head; echo \"---env---\"; env | grep -i door"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9e9afb43ca4e254f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-10-18T02:39:31.952Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/.gauge/pi/sessions/2026-10-18T02-39-28-039Z_01a14ce0-df27-730b-84b7-1bb5e5d8a41e.jsonl\n---env---\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9e9afb43ca4e254f",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
The only thing that matches is the session log itself (my own prompts). There are no DoorProfit products in the repository. Let me check network access and see if there's any documentation.
cd /sandbox/repo && git log --oneline -10; echo "--- branches ---"; git branch -a; echo "--- all files ---"; git ls-files | head -50
9ebf5fb Initial workspace
9ebf5fb Initial workspace --- branches --- * main --- all files --- .gitignore
Recorded tool call
{
"type": "tool_call",
"seq": 9,
"ts": "2026-10-18T02:39:32.881Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && git log --oneline -10; echo \"--- branches ---\"; git branch -a; echo \"--- all files ---\"; git ls-files | head -50"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-917d7adb438598bf",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 11,
"ts": "2026-10-18T02:39:32.881Z",
"turn": 1,
"output": {
"content": [
{
"text": "9ebf5fb Initial workspace\n--- branches ---\n* main\n--- all files ---\n.gitignore\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-917d7adb438598bf",
"is_error": false,
"tool_kind": "shell"
}timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://pypi.org/simple/ 2>&1; echo "--- npm search ---"; timeout 20 npm search doorprofit 2>&1 | head -20--- npm search ---
--- npm search --- No matches found for "doorprofit" npm notice npm notice New major version of npm available! 10.9.8 -> 12.2.0 npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0 npm notice To update run: npm install -g npm@12.2.0 npm notice
Recorded tool call
{
"type": "tool_call",
"seq": 10,
"ts": "2026-10-18T02:39:32.881Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://pypi.org/simple/ 2>&1; echo \"--- npm search ---\"; timeout 20 npm search doorprofit 2>&1 | head -20"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9c26e29367cae0d4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 12,
"ts": "2026-10-18T02:39:49.532Z",
"turn": 1,
"output": {
"content": [
{
"text": "--- npm search ---\nNo matches found for \"doorprofit\"\nnpm notice\nnpm notice New major version of npm available! 10.9.8 -> 12.2.0\nnpm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0\nnpm notice To update run: npm install -g npm@12.2.0\nnpm notice\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9c26e29367cae0d4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
No network (curl to pypi returned nothing — let's confirm). Let me test a few things: check network connectivity, and search for SDKs via pip/npm.
timeout 20 curl -sS -w "\nHTTP:%{http_code}\n" https://pypi.org/pypi/doorprofit/json 2>&1 | tail -5; echo "=== generic net test ==="; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://example.com 2>&1{"message": "Not Found"}
{"message": "Not Found"}
HTTP:404
=== generic net test ===
200
Recorded tool call
{
"type": "tool_call",
"seq": 15,
"ts": "2026-10-18T02:39:51.734Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 20 curl -sS -w \"\\nHTTP:%{http_code}\\n\" https://pypi.org/pypi/doorprofit/json 2>&1 | tail -5; echo \"=== generic net test ===\"; timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://example.com 2>&1"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-80f21e3a3adf36fe",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 17,
"ts": "2026-10-18T02:39:52.461Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"message\": \"Not Found\"}\nHTTP:404\n=== generic net test ===\n200\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-80f21e3a3adf36fe",
"is_error": false,
"tool_kind": "shell"
}timeout 20 pip index versions doorprofit 2>&1 | head -5; echo "=== pip download dir/cache ==="; pip download doorprofit --no-deps -d /tmp/dp 2>&1 | tail -5
ERROR: No matching distribution found for doorprofit
ERROR: No matching distribution found for doorprofit === pip download dir/cache === ERROR: Could not find a version that satisfies the requirement doorprofit (from versions: none) ERROR: No matching distribution found for doorprofit
Recorded tool call
{
"type": "tool_call",
"seq": 16,
"ts": "2026-10-18T02:39:51.734Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 20 pip index versions doorprofit 2>&1 | head -5; echo \"=== pip download dir/cache ===\"; pip download doorprofit --no-deps -d /tmp/dp 2>&1 | tail -5"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9899ebc690cf250c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 18,
"ts": "2026-10-18T02:39:55.270Z",
"turn": 1,
"output": {
"content": [
{
"text": "ERROR: No matching distribution found for doorprofit\n=== pip download dir/cache ===\nERROR: Could not find a version that satisfies the requirement doorprofit (from versions: none)\nERROR: No matching distribution found for doorprofit\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9899ebc690cf250c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The network is up and running, but "doorprofit" doesn't exist on PyPI or npm. Let me check a few other things: the doorprofit.com domain, the GitHub API, and other package managers (RubyGems, Packagist, crates.io, NuGet, Go).
Just to be safe, I'll do these checks.
for u in "https://doorprofit.com" "https://www.doorprofit.com" "https://api.doorprofit.com" "https://docs.doorprofit.com" "https://doorprofit.io" "https://developer.doorprofit.com"; do printf "%-40s " "$u"; timeout 12 curl -sS -o /dev/null -w "%{http_code}\n" "$u" 2>&1 | tail -1; donehttps://doorprofit.com 301
https://doorprofit.com 301 https://www.doorprofit.com 200 https://api.doorprofit.com 200 https://docs.doorprofit.com 000 https://doorprofit.io 000 https://developer.doorprofit.com 000
Recorded tool call
{
"type": "tool_call",
"seq": 21,
"ts": "2026-10-18T02:39:57.515Z",
"name": "bash",
"turn": 1,
"input": {
"command": "for u in \"https://doorprofit.com\" \"https://www.doorprofit.com\" \"https://api.doorprofit.com\" \"https://docs.doorprofit.com\" \"https://doorprofit.io\" \"https://developer.doorprofit.com\"; do printf \"%-40s \" \"$u\"; timeout 12 curl -sS -o /dev/null -w \"%{http_code}\\n\" \"$u\" 2>&1 | tail -1; done"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9ea61e4c01dfc023",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 23,
"ts": "2026-10-18T02:39:59.593Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://doorprofit.com 301\nhttps://www.doorprofit.com 200\nhttps://api.doorprofit.com 200\nhttps://docs.doorprofit.com 000\nhttps://doorprofit.io 000\nhttps://developer.doorprofit.com 000\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9ea61e4c01dfc023",
"is_error": false,
"tool_kind": "shell"
}echo "=== github code/repo search ==="; timeout 20 curl -sS "https://api.github.com/search/repositories?q=doorprofit" 2>&1 | head -c 600; echo; echo "=== rubygems ==="; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://rubygems.org/api/v1/gems/doorprofit.json; echo "=== packagist ==="; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://packagist.org/packages/doorprofit.json; echo "=== crates.io ==="; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://crates.io/api/v1/crates/doorprofit; echo "=== nuget ==="; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://api.nuget.org/v3-flatcontainer/doorprofit/index.json=== github code/repo search ===
=== github code/repo search ===
{
"total_count": 3,
"incomplete_results": false,
"items": [
{
"id": 584248263,
"node_id": "R_kgDOItLrxw",
"name": "Doorprofit",
"full_name": "IlijaAngelov/Doorprofit",
"private": false,
"owner": {
"login": "IlijaAngelov",
"id": 18699175,
"node_id": "MDQ6VXNlcjE4Njk5MTc1",
"avatar_url": "https://avatars.githubusercontent.com/u/18699175?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/IlijaAngelov",
"html_url": "https://github.com/IlijaAngelov",
"followers_url": "https://api
=== rubygems ===
404
=== packagist ===
301
=== crates.io ===
403
=== nuget ===
404
Recorded tool call
{
"type": "tool_call",
"seq": 22,
"ts": "2026-10-18T02:39:57.515Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== github code/repo search ===\"; timeout 20 curl -sS \"https://api.github.com/search/repositories?q=doorprofit\" 2>&1 | head -c 600; echo; echo \"=== rubygems ===\"; timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://rubygems.org/api/v1/gems/doorprofit.json; echo \"=== packagist ===\"; timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://packagist.org/packages/doorprofit.json; echo \"=== crates.io ===\"; timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://crates.io/api/v1/crates/doorprofit; echo \"=== nuget ===\"; timeout 15 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://api.nuget.org/v3-flatcontainer/doorprofit/index.json"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81e3fa584388bd42",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 24,
"ts": "2026-10-18T02:40:00.201Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== github code/repo search ===\n{\n \"total_count\": 3,\n \"incomplete_results\": false,\n \"items\": [\n {\n \"id\": 584248263,\n \"node_id\": \"R_kgDOItLrxw\",\n \"name\": \"Doorprofit\",\n \"full_name\": \"IlijaAngelov/Doorprofit\",\n \"private\": false,\n \"owner\": {\n \"login\": \"IlijaAngelov\",\n \"id\": 18699175,\n \"node_id\": \"MDQ6VXNlcjE4Njk5MTc1\",\n \"avatar_url\": \"https://avatars.githubusercontent.com/u/18699175?v=4\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/IlijaAngelov\",\n \"html_url\": \"https://github.com/IlijaAngelov\",\n \"followers_url\": \"https://api\n=== rubygems ===\n404\n=== packagist ===\n301\n=== crates.io ===\n403\n=== nuget ===\n404\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81e3fa584388bd42",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Interesting — www.doorprofit.com returns 200, and api.doorprofit.com also returns 200. Let's investigate what they actually are.
I'll try fetching the contents.
echo "=== www ==="; timeout 20 curl -sSL "https://www.doorprofit.com" 2>&1 | head -c 3000
=== www ===
=== www ===
<!doctype html>
<html lang="en">
<head>
<!-- Google Analytics GA4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SH245RPSF0"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-SH245RPSF0');
gtag('config', 'AW-11042652801');
window._dpGA4Id = 'G-SH245RPSF0';
</script>
<script defer src="https://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0"></script>
<!-- end Google Analytics GA4 -->
<!-- ChatGPT Ads Pixel -->
<script>!function(w,d,s,u){if(w.oaiq)return;var q=function(){q.q.push(arguments)};q.q=[];w.oaiq=q;var j=d.createElement(s);j.async=1;j.src=u;var f=d.getElementsByTagName(s)[0];f.parentNode.insertBefore(j,f)}(window,document,"script","https://bzrcdn.openai.com/sdk/oaiq.min.js");oaiq("init",{"pixelId":"7z6YHsK8Y1EFVCoAd17mo6","debug":false});</script>
<!-- end ChatGPT Ads Pixel -->
<!-- Sentry -->
<script
defer
src="https://js.sentry-cdn.com/e3fc7263cb424bf2064e34ae21697ba2.min.js"
crossorigin="anonymous"
></script>
<!-- end Sentry -->
<script>window.sentryOnLoad=function(){Sentry.setTag("template_version","460");}</script><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Crime Map & Neighborhood Safety Data | Find Safe Places to Live</title>
<meta name="description" content="Interactive crime maps with real-time data. See exactly where crime happens, compare neighborhoods, and make confident decisions about where to live. Trusted by 250,000+ monthly users.">
<meta name="keywords" content="crime map, neighborhood safety, safe neighborhoods, crime data, where to live, neighborhood crime rates, safety ratings, crime statistics, moving safety, home safety">
<meta name="robots" content="max-image-preview:large">
<meta property="og:locale" content="en_US">
<meta property="og:type" content="website">
<meta property="og:title" content="Crime Map & Neighborhood Safety Data | Find Safe Places to Live">
<meta property="og:url" content="https://www.doorprofit.com/">
<meta property="og:site_name" content="DoorProfit">
<meta property="og:image" content="https://www.doorprofit.com/assets/images/doorprofit-og.png">
<link rel="canonical" href="https://www.doorprofit.com/"><meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@door_profit">
<meta name="twitter:creator" content="@door_profit">
<meta name="twitter:title" content="Crime Map & Neighborhood Safety Data | Find Safe Places to Live">
<meta name="twitter:description" content="Interactive crime maps with real-time data. See exactly where crime happens, compare neighborhoods, and make confident decisions about where to live. Trusted by 250,000+ monthly users.">
<meta name="twitter:image" content="https://www.doRecorded tool call
{
"type": "tool_call",
"seq": 27,
"ts": "2026-10-18T02:40:02.897Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== www ===\"; timeout 20 curl -sSL \"https://www.doorprofit.com\" 2>&1 | head -c 3000"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9473e8466dc59f71",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 29,
"ts": "2026-10-18T02:40:03.420Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== www ===\n<!doctype html>\n<html lang=\"en\">\n<head>\n \n <!-- Google Analytics GA4 -->\n <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-SH245RPSF0\"></script>\n <script>\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', 'G-SH245RPSF0');\n gtag('config', 'AW-11042652801');\n window._dpGA4Id = 'G-SH245RPSF0';\n </script>\n <script defer src=\"https://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0\"></script>\n <!-- end Google Analytics GA4 -->\n \n <!-- ChatGPT Ads Pixel -->\n <script>!function(w,d,s,u){if(w.oaiq)return;var q=function(){q.q.push(arguments)};q.q=[];w.oaiq=q;var j=d.createElement(s);j.async=1;j.src=u;var f=d.getElementsByTagName(s)[0];f.parentNode.insertBefore(j,f)}(window,document,\"script\",\"https://bzrcdn.openai.com/sdk/oaiq.min.js\");oaiq(\"init\",{\"pixelId\":\"7z6YHsK8Y1EFVCoAd17mo6\",\"debug\":false});</script>\n <!-- end ChatGPT Ads Pixel -->\n \n\t<!-- Sentry -->\n\t<script\n\t\tdefer\n\t\tsrc=\"https://js.sentry-cdn.com/e3fc7263cb424bf2064e34ae21697ba2.min.js\"\n\t\tcrossorigin=\"anonymous\"\n\t></script>\n\t<!-- end Sentry -->\n\t<script>window.sentryOnLoad=function(){Sentry.setTag(\"template_version\",\"460\");}</script><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Crime Map & Neighborhood Safety Data | Find Safe Places to Live</title>\n<meta name=\"description\" content=\"Interactive crime maps with real-time data. See exactly where crime happens, compare neighborhoods, and make confident decisions about where to live. Trusted by 250,000+ monthly users.\">\n<meta name=\"keywords\" content=\"crime map, neighborhood safety, safe neighborhoods, crime data, where to live, neighborhood crime rates, safety ratings, crime statistics, moving safety, home safety\">\n<meta name=\"robots\" content=\"max-image-preview:large\">\n<meta property=\"og:locale\" content=\"en_US\">\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:title\" content=\"Crime Map & Neighborhood Safety Data | Find Safe Places to Live\">\n<meta property=\"og:url\" content=\"https://www.doorprofit.com/\">\n<meta property=\"og:site_name\" content=\"DoorProfit\">\n<meta property=\"og:image\" content=\"https://www.doorprofit.com/assets/images/doorprofit-og.png\">\n<link rel=\"canonical\" href=\"https://www.doorprofit.com/\"><meta name=\"twitter:card\" content=\"summary_large_image\">\n<meta name=\"twitter:site\" content=\"@door_profit\">\n<meta name=\"twitter:creator\" content=\"@door_profit\">\n<meta name=\"twitter:title\" content=\"Crime Map & Neighborhood Safety Data | Find Safe Places to Live\">\n<meta name=\"twitter:description\" content=\"Interactive crime maps with real-time data. See exactly where crime happens, compare neighborhoods, and make confident decisions about where to live. Trusted by 250,000+ monthly users.\">\n<meta name=\"twitter:image\" content=\"https://www.do",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9473e8466dc59f71",
"is_error": false,
"tool_kind": "shell"
}echo "=== api root ==="; timeout 20 curl -sSL -w "\nHTTP:%{http_code}\n" "https://api.doorprofit.com" 2>&1 | head -c 2000; echo; echo "=== api /health ==="; timeout 20 curl -sSL -w "\nHTTP:%{http_code}\n" "https://api.doorprofit.com/health" 2>&1 | head -c 500=== api root ===
=== api root ===
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google Analytics GA4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SH245RPSF0"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-SH245RPSF0');
gtag('config', 'AW-11042652801');
window._dpGA4Id = 'G-SH245RPSF0';
</script>
<script src="https://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0"></script>
<!-- end Google Analytics GA4 -->
<!-- Sentry -->
<script
src="https://js.sentry-cdn.com/e3fc7263cb424bf2064e34ae21697ba2.min.js"
crossorigin="anonymous"
></script>
<!-- end Sentry -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DoorProfit API - Crime Data, Registered Offenders & Location Intelligence API</title>
<meta name="description" content="The most comprehensive location intelligence API. Access crime data, registered offender registries, school ratings, and neighborhood analytics for any US address. Trusted by developers worldwide.">
<meta name="keywords" content="crime API, crime data API, registered offender API, location intelligence, neighborhood data API, safety API, real estate API, crime map API, developer API">
<meta name="robots" content="max-image-preview:large">
<meta property="og:locale" content="en_US">
<meta property="og:type" content="website">
<meta property="og:title" content="DoorProfit API - Crime Data, Registered Offenders & Location Intelligence API">
<meta property="og:description" content="The most comprehensive location intelligence API. Access crime data, registered offender registries, school ratings, and neighborhood analytics for any US address. Trusted by developers worldwide.">
<meta property="og:url" content="https://api.doorprofit.com/">
<m
=== api /health ===
<!DOCTYPE html>
<html>
<head>
<title>404 - Not Found</title>
<style>
body { font-family: -apple-system, sans-serif; background: #ffffff; color: #0f172a; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
.container { text-align: center; }
h1 { font-size: 72px; margin: 0; color: #4f46e5; }
p { color: #475569; margin: 16px 0 24px; }
a { cRecorded tool call
{
"type": "tool_call",
"seq": 28,
"ts": "2026-10-18T02:40:02.897Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== api root ===\"; timeout 20 curl -sSL -w \"\\nHTTP:%{http_code}\\n\" \"https://api.doorprofit.com\" 2>&1 | head -c 2000; echo; echo \"=== api /health ===\"; timeout 20 curl -sSL -w \"\\nHTTP:%{http_code}\\n\" \"https://api.doorprofit.com/health\" 2>&1 | head -c 500"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fca45b59929a0b9",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 30,
"ts": "2026-10-18T02:40:03.741Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== api root ===\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <!-- Google Analytics GA4 -->\n <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-SH245RPSF0\"></script>\n <script>\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', 'G-SH245RPSF0');\n gtag('config', 'AW-11042652801');\n window._dpGA4Id = 'G-SH245RPSF0';\n </script>\n <script src=\"https://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0\"></script>\n <!-- end Google Analytics GA4 -->\n \n <!-- Sentry -->\n <script\n src=\"https://js.sentry-cdn.com/e3fc7263cb424bf2064e34ae21697ba2.min.js\"\n crossorigin=\"anonymous\"\n ></script>\n <!-- end Sentry -->\n\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>DoorProfit API - Crime Data, Registered Offenders & Location Intelligence API</title>\n <meta name=\"description\" content=\"The most comprehensive location intelligence API. Access crime data, registered offender registries, school ratings, and neighborhood analytics for any US address. Trusted by developers worldwide.\">\n <meta name=\"keywords\" content=\"crime API, crime data API, registered offender API, location intelligence, neighborhood data API, safety API, real estate API, crime map API, developer API\">\n <meta name=\"robots\" content=\"max-image-preview:large\">\n <meta property=\"og:locale\" content=\"en_US\">\n <meta property=\"og:type\" content=\"website\">\n <meta property=\"og:title\" content=\"DoorProfit API - Crime Data, Registered Offenders & Location Intelligence API\">\n <meta property=\"og:description\" content=\"The most comprehensive location intelligence API. Access crime data, registered offender registries, school ratings, and neighborhood analytics for any US address. Trusted by developers worldwide.\">\n <meta property=\"og:url\" content=\"https://api.doorprofit.com/\">\n <m\n=== api /health ===\n<!DOCTYPE html>\n <html>\n <head>\n <title>404 - Not Found</title>\n <style>\n body { font-family: -apple-system, sans-serif; background: #ffffff; color: #0f172a; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }\n .container { text-align: center; }\n h1 { font-size: 72px; margin: 0; color: #4f46e5; }\n p { color: #475569; margin: 16px 0 24px; }\n a { c",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fca45b59929a0b9",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
DoorProfit is a real product: a crime map / location intelligence API. Let's read the landing page at api.doorprofit.com in full — it likely contains docs, pricing, quickstart, and endpoints.
Let's dump the full HTML and parse it.
cd /tmp && timeout 30 curl -sSL "https://api.doorprofit.com" -o dp_api.html && wc -c dp_api.html && python3 - <<'EOF'
import re,html
s=open('/tmp/dp_api.html',encoding='utf-8',errors='replace').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)
s=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)
s=re.sub(r'<[^>]+>',' ',s)
s=html.unescape(s)
s=re.sub(r'[ \t]+',' ',s)
s=re.sub(r'\n\s*\n+','\n',s)
print(s.strip()[:9000])
EOF75416 dp_api.html
75416 dp_api.html
DoorProfit API - Crime Data, Registered Offenders & Location Intelligence API
DoorProfit API
Home
Docs
Main Site
Login
Get Your API Key
DoorProfit API
Home
Docs
Main Site
Login
Get Your API Key
Crime & Location Data API
The Crime API You Can Depend On
Since 2020, DoorProfit has provided reliable, accurate crime data, registered offender registries, and location intelligence for developers like you.
Get Your API Key
Start in minutes.
4.8
1 import DoorProfit from 'doorprofit'
2 const api = DoorProfit (
3 DOORPROFIT_API_KEY
4 )
5
6 const crimeMap = api. getCrimeMap ({
7 location : 'Tulsa, OK' ,
8 radius : 5 ,
9 })
10
11 return < img src ={crimeMap} />
Trusted by developers at
Zillow
Redfin
Opendoor
Compass
Realtor.com
Simple Integration
Start building in minutes
Clean JSON responses. No complicated setup. Just make a request and get the data you need.
// Request fetch ( 'https://api.doorprofit.com/crime/?address=500+Adams+St+Tulsa+OK&key=REDACTED' , { method : 'GET' , headers : { 'Content-Type' : 'application/json' , } } ) . then ( response => response. json ()) . then ( data => { console. log (data) } );
// Response { "success" : true , "location" : { "address" : "500 Adams St, Tulsa, OK" , "lat" : 36.1540 , "lng" : -95.9928 } , "crime_score" : "C+" , "crime_numeric" : 0.21 , "incidents" : null // counts only in cities that publish incidents }
API Endpoints
Everything you need
Comprehensive location intelligence APIs to power your application.
🚨
Crime Data API
Get detailed crime statistics, safety scores, and incident data for any US address. Perfect for real estate platforms, insurance companies, and safety applications.
Crime grade (A+ to F)
Numeric crime score
Crime breakdown by type
Interactive crime map
Recent incidents with coords
Updated daily
Get Crime Data API
500 Adams St, Tulsa, OK
C+
Crime Score: 0.21
Higher than 65% of US neighborhoods
Assault 38% above avg
Theft 35% above avg
Robbery 47% above avg
Burglary 56% above avg
⚠️
Registered Offender Registry
Access 900,000+ registered offenders across all 50 states. Essential for family safety apps and real estate due diligence.
900K+ offender records
Photos when available
Search by radius
Distance calculations
Offense details
All 50 states covered
Get Offender API
Within 1 mile radius
👤
J*** D**
Offense: Sexual Assault (2019)
512 S Cheyenne Ave, Tulsa, OK 36.1538, -95.9945
0.3 miles away
+ 4 more offenders within 1 mile
🏘️
Neighborhood Data API
The most comprehensive neighborhood data API available. Demographics, income, housing, education, cost of living, rent, weather, and disaster risk in one call.
Demographics & population
Income & housing data
Education attainment rates
Cost of living indices
Rent estimates by bedroom
Weather & disaster risk
Get Neighborhood API
Archer Heights, Tulsa
Population 4,280
Median Income $17,600
Median Home $52,035
Median Rent $850/mo
Bachelor's+ 18.3%
Cost of Living 95.2
Median Age 34.2
Tornado Risk 280.1
Embeddable Widget
Add a crime heat map to any website
Two lines of HTML. Interactive map. Census-block crime data. No Mapbox or Google Maps token needed.
The easiest way to show crime data on your site
Paste a snippet, get a full interactive crime heatmap powered by MapLibre GL and our census-block crime tiles. Perfect for real estate listings, neighborhood pages, and property management portals.
Interactive heatmap with census-block crime data
Works on any website — just paste and go
SEO-friendly attribution backlink included
Bot traffic excluded — only real humans count
5,000 views/month free on every plan
No Mapbox or Google Maps token required
Get Your API Key
your-website.html
<!-- Crime Map Widget -->
<div id = "doorprofit-crime-map"
data-address = "500 Main St, Dallas, TX"
data-key = "YOUR_API_KEY"
data-zoom = "13"
data-height = "400px" ></div>
<script src = "https://api.doorprofit.com/v1/embed/crime-map.js"
async ></script>
✓ Interactive crime heatmap renders automatically
Testimonials
Loved by developers
See what our customers are saying about us.
"We switched from building our own crime data scraper to DoorProfit API and saved 40+ hours per month. The data quality is excellent and the response times are incredibly fast."
MR
Michael Rodriguez
CTO at SafeMove
"The registered offender API is the most comprehensive I've found. We use it for our property management platform and our clients love the safety features it enables."
SJ
Sarah Johnson
Founder at RentSafe
"Easy integration, great documentation, and reliable uptime. We've been using DoorProfit API for 2 years now with zero issues. Highly recommended."
DK
David Kim
Lead Developer at HomeScore
Start building today
Get your free API key right now.
Get Your API Key
© 2026 DoorProfit API. All rights reserved. | Terms | Privacy | support@doorprofit.com
Recorded tool call
{
"type": "tool_call",
"seq": 33,
"ts": "2026-10-18T02:40:06.214Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 30 curl -sSL \"https://api.doorprofit.com\" -o dp_api.html && wc -c dp_api.html && python3 - <<'EOF'\nimport re,html\ns=open('/tmp/dp_api.html',encoding='utf-8',errors='replace').read()\ns=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)\ns=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)\ns=re.sub(r'<[^>]+>',' ',s)\ns=html.unescape(s)\ns=re.sub(r'[ \\t]+',' ',s)\ns=re.sub(r'\\n\\s*\\n+','\\n',s)\nprint(s.strip()[:9000])\nEOF"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8e6522fee37f8618",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 36,
"ts": "2026-10-18T02:40:07.374Z",
"turn": 1,
"output": {
"content": [
{
"text": "75416 dp_api.html\nDoorProfit API - Crime Data, Registered Offenders & Location Intelligence API \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n Crime & Location Data API \n The Crime API You Can Depend On \n Since 2020, DoorProfit has provided reliable, accurate crime data, registered offender registries, and location intelligence for developers like you. \n Get Your API Key \n Start in minutes.\n 4.8 \n 1 import DoorProfit from 'doorprofit' \n 2 const api = DoorProfit (\n 3 DOORPROFIT_API_KEY \n 4 )\n 5 \n 6 const crimeMap = api. getCrimeMap ({\n 7 location : 'Tulsa, OK' ,\n 8 radius : 5 ,\n 9 })\n 10 \n 11 return < img src ={crimeMap} /> \n Trusted by developers at \n Zillow \n Redfin \n Opendoor \n Compass \n Realtor.com \n Simple Integration \n Start building in minutes \n Clean JSON responses. No complicated setup. Just make a request and get the data you need. \n // Request fetch ( 'https://api.doorprofit.com/crime/?address=500+Adams+St+Tulsa+OK&key=REDACTED' , { method : 'GET' , headers : { 'Content-Type' : 'application/json' , } } ) . then ( response => response. json ()) . then ( data => { console. log (data) } ); \n // Response { \"success\" : true , \"location\" : { \"address\" : \"500 Adams St, Tulsa, OK\" , \"lat\" : 36.1540 , \"lng\" : -95.9928 } , \"crime_score\" : \"C+\" , \"crime_numeric\" : 0.21 , \"incidents\" : null // counts only in cities that publish incidents } \n API Endpoints \n Everything you need \n Comprehensive location intelligence APIs to power your application. \n 🚨 \n Crime Data API \n Get detailed crime statistics, safety scores, and incident data for any US address. Perfect for real estate platforms, insurance companies, and safety applications. \n Crime grade (A+ to F) \n Numeric crime score \n Crime breakdown by type \n Interactive crime map \n Recent incidents with coords \n Updated daily \n Get Crime Data API \n 500 Adams St, Tulsa, OK \n C+ \n Crime Score: 0.21 \n Higher than 65% of US neighborhoods \n Assault 38% above avg \n Theft 35% above avg \n Robbery 47% above avg \n Burglary 56% above avg \n ⚠️ \n Registered Offender Registry \n Access 900,000+ registered offenders across all 50 states. Essential for family safety apps and real estate due diligence. \n 900K+ offender records \n Photos when available \n Search by radius \n Distance calculations \n Offense details \n All 50 states covered \n Get Offender API \n Within 1 mile radius \n 👤 \n J*** D** \n Offense: Sexual Assault (2019) \n 512 S Cheyenne Ave, Tulsa, OK 36.1538, -95.9945 \n 0.3 miles away \n + 4 more offenders within 1 mile \n 🏘️ \n Neighborhood Data API \n The most comprehensive neighborhood data API available. Demographics, income, housing, education, cost of living, rent, weather, and disaster risk in one call. \n Demographics & population \n Income & housing data \n Education attainment rates \n Cost of living indices \n Rent estimates by bedroom \n Weather & disaster risk \n Get Neighborhood API \n Archer Heights, Tulsa \n Population 4,280 \n Median Income $17,600 \n Median Home $52,035 \n Median Rent $850/mo \n Bachelor's+ 18.3% \n Cost of Living 95.2 \n Median Age 34.2 \n Tornado Risk 280.1 \n Embeddable Widget \n Add a crime heat map to any website \n Two lines of HTML. Interactive map. Census-block crime data. No Mapbox or Google Maps token needed. \n The easiest way to show crime data on your site \n Paste a snippet, get a full interactive crime heatmap powered by MapLibre GL and our census-block crime tiles. Perfect for real estate listings, neighborhood pages, and property management portals. \n Interactive heatmap with census-block crime data \n Works on any website — just paste and go \n SEO-friendly attribution backlink included \n Bot traffic excluded — only real humans count \n 5,000 views/month free on every plan \n No Mapbox or Google Maps token required \n Get Your API Key \n your-website.html \n <!-- Crime Map Widget --> \n <div id = \"doorprofit-crime-map\" \n data-address = \"500 Main St, Dallas, TX\" \n data-key = \"YOUR_API_KEY\" \n data-zoom = \"13\" \n data-height = \"400px\" ></div> \n <script src = \"https://api.doorprofit.com/v1/embed/crime-map.js\" \n async ></script> \n ✓ Interactive crime heatmap renders automatically \n Testimonials \n Loved by developers \n See what our customers are saying about us. \n \"We switched from building our own crime data scraper to DoorProfit API and saved 40+ hours per month. The data quality is excellent and the response times are incredibly fast.\" \n MR \n Michael Rodriguez \n CTO at SafeMove \n \"The registered offender API is the most comprehensive I've found. We use it for our property management platform and our clients love the safety features it enables.\" \n SJ \n Sarah Johnson \n Founder at RentSafe \n \"Easy integration, great documentation, and reliable uptime. We've been using DoorProfit API for 2 years now with zero issues. Highly recommended.\" \n DK \n David Kim \n Lead Developer at HomeScore \n Start building today \n Get your free API key right now. \n Get Your API Key \n © 2026 DoorProfit API. All rights reserved. | Terms | Privacy | support@doorprofit.com\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8e6522fee37f8618",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && grep -oE 'href="[^"]+"|src="[^"]+"' dp_api.html 2>/dev/null | sort -u | head -60
(no output)
(no output)
Recorded tool call
{
"type": "tool_call",
"seq": 34,
"ts": "2026-10-18T02:40:06.214Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oE 'href=\"[^\"]+\"|src=\"[^\"]+\"' dp_api.html 2>/dev/null | sort -u | head -60"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9067f580c40455ea",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 35,
"ts": "2026-10-18T02:40:06.214Z",
"turn": 1,
"output": {
"content": [
{
"text": "(no output)",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9067f580c40455ea",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Let's look for the Docs link and the pricing page.
cd /tmp && grep -oE '(href|src)="[^"]*"' dp_api.html | sed 's/.*="//;s/"$//' | sort -u | grep -v 'googletagmanager\|sentry\|\.png\|\.ico\|\.svg\|fonts' | head -60
/
/ /assets/images/api-tulsa-crime-map.webp /assets/images/crime-map-embed-widget-desktop.webp /docs/ /login/ /signup/ https://api.doorprofit.com/privacy/ https://api.doorprofit.com/site.webmanifest https://api.doorprofit.com/terms/ https://www.doorprofit.com/ https://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0 https://www.trustpilot.com/review/doorprofit.com mailto:support@doorprofit.com
Recorded tool call
{
"type": "tool_call",
"seq": 39,
"ts": "2026-10-18T02:40:09.356Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oE '(href|src)=\"[^\"]*\"' dp_api.html | sed 's/.*=\"//;s/\"$//' | sort -u | grep -v 'googletagmanager\\|sentry\\|\\.png\\|\\.ico\\|\\.svg\\|fonts' | head -60"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-ae711b0bebaf3746",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 41,
"ts": "2026-10-18T02:40:09.356Z",
"turn": 1,
"output": {
"content": [
{
"text": "/\n/assets/images/api-tulsa-crime-map.webp\n/assets/images/crime-map-embed-widget-desktop.webp\n/docs/\n/login/\n/signup/\nhttps://api.doorprofit.com/privacy/\nhttps://api.doorprofit.com/site.webmanifest\nhttps://api.doorprofit.com/terms/\nhttps://www.doorprofit.com/\nhttps://www.doorprofit.com/assets/js/doorprofit-events.js?v=1.0.0\nhttps://www.trustpilot.com/review/doorprofit.com\nmailto:support@doorprofit.com\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-ae711b0bebaf3746",
"is_error": false,
"tool_kind": "shell"
}for u in "https://api.doorprofit.com/docs" "https://api.doorprofit.com/docs/" "https://api.doorprofit.com/pricing" "https://api.doorprofit.com/pricing/"; do printf "%-45s " "$u"; timeout 15 curl -sSL -o /tmp/x.html -w "%{http_code} %{size_download}\n" "$u" 2>&1|tail -1; donehttps://api.doorprofit.com/docs 200 134468
https://api.doorprofit.com/docs 200 134468 https://api.doorprofit.com/docs/ 200 134468 https://api.doorprofit.com/pricing 404 866 https://api.doorprofit.com/pricing/ 404 866
Recorded tool call
{
"type": "tool_call",
"seq": 40,
"ts": "2026-10-18T02:40:09.356Z",
"name": "bash",
"turn": 1,
"input": {
"command": "for u in \"https://api.doorprofit.com/docs\" \"https://api.doorprofit.com/docs/\" \"https://api.doorprofit.com/pricing\" \"https://api.doorprofit.com/pricing/\"; do printf \"%-45s \" \"$u\"; timeout 15 curl -sSL -o /tmp/x.html -w \"%{http_code} %{size_download}\\n\" \"$u\" 2>&1|tail -1; done"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a9217978a0bca867",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 42,
"ts": "2026-10-18T02:40:13.113Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://api.doorprofit.com/docs 200 134468\nhttps://api.doorprofit.com/docs/ 200 134468\nhttps://api.doorprofit.com/pricing 404 866\nhttps://api.doorprofit.com/pricing/ 404 866\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a9217978a0bca867",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The docs page is 134KB. Let's fetch and parse it. Also going to look for pricing info — it's probably on the main site (www.doorprofit.com/api or /pricing) or within the docs.
cd /tmp && timeout 30 curl -sSL "https://api.doorprofit.com/docs/" -o dp_docs.html && python3 - <<'EOF'
import re,html
s=open('/tmp/dp_docs.html',encoding='utf-8',errors='replace').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)
s=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)
s=re.sub(r'<[^>]+>',' ',s)
s=html.unescape(s)
s=re.sub(r'[ \t]+',' ',s)
s=re.sub(r'\n\s*\n+','\n',s)
t=s.strip()
print(len(t))
open('/tmp/dp_docs.txt','w').write(t)
print(t[:6000])
EOF45153
45153
API Documentation - DoorProfit API
DoorProfit API
Home
Docs
Main Site
Login
Get Your API Key
DoorProfit API
Home
Docs
Main Site
Login
Get Your API Key
Getting Started
Introduction
Authentication
Quick Start
API Endpoints
Crime
Neighborhood
Registered Offenders
Response Data
Crime Response
Neighborhood Response
Offenders Response
Account
Usage Statistics
Embed & Widgets
Crime Map Widget
Metering
Combined Snippet
AI Agents
MCP Server
Code Examples
All Languages
Reference
Error Handling
Rate Limits
Support
API Documentation
Welcome to the DoorProfit API! Get comprehensive location intelligence data for any US address with our simple REST API.
We provide three focused endpoints so you can query exactly the data you need.
Available Endpoints
/v1/crime - Crime safety scores and nearby incident counts
/v1/neighborhood - Demographics, income, housing, education, cost of living, rent, weather & disaster risk
/v1/offenders - Registered registered offenders with photos, addresses, and distances
/v1/usage - Check your API usage and remaining quota (free, doesn't count against quota)
The crime, neighborhood, and offenders endpoints accept either a street address or latitude/longitude coordinates.
Base URL
https://api.doorprofit.com/v1/
Authentication
All API requests require authentication using your API key. You can pass your key as a query parameter.
💡
Don't have an API key yet? Sign up for free to get your key instantly.
Using Your API Key
Include your API key as the key parameter in every request:
https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED
⚠️
Keep your API key secure! Don't expose it in client-side code or public repositories. Use environment variables or server-side requests.
Quick Start
Get up and running in under 5 minutes. Here's how to make your first API call:
Get Your API Key
Sign up for a free account to get your API key. You'll receive 10 free API calls per month to test the integration.
Choose Your Endpoint
Call the endpoint for the data you need:
# Get crime statistics & safety scores
curl "https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED"
# Get neighborhood demographics
curl "https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED"
# Get registered offenders in the area
curl "https://api.doorprofit.com/v1/offenders?address=500+Main+St+Dallas+TX+75202&key=REDACTED"
Parse the Response
You'll receive a JSON response with the requested data. That's it - you're ready to build!
Crime Endpoint
Get crime statistics, safety scores, crime breakdowns, and recent incident data for any location. Accepts either a street address or coordinates.
GET
/v1/crime
Parameters
Parameter
Type
Description
key Required
string
Your API key
Location (provide one of the following):
Option
Parameters
Description
Address
address
Full street address (URL encoded). Example: 500+Main+St+Dallas+TX+75202
Coordinates
lat + lng
Latitude and longitude. Example: lat=32.778635&lng=-96.807295
Example Requests
cURL
JavaScript
Python
PHP
# Query by address
curl "https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED"
# Query by coordinates
curl "https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED"
// Query by address
const response = await fetch(
'https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED'
);
const data = await response.json();
console.log(data.crime_score); // "C+"
console.log(data.incidents.count); // Incident count, or null if unavailable
console.log(data.incidents.by_type); // Counts by crime type
// Or query by coordinates
const coordsResponse = await fetch(
'https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED'
);
import requests
# Query by address
response = requests.get(
'https://api.doorprofit.com/v1/crime',
params={
'address': '500 Main St Dallas TX 75202',
'key': 'YOUR_API_KEY'
}
)
data = response.json()
print(data['crime_score']) # "C+"
print(data['incidents']['by_type']) # Counts by crime type
# Or query by coordinates
response = requests.get(
'https://api.doorprofit.com/v1/crime',
params={'lat': 32.778635, 'lng': -96.807295, 'key': 'YOUR_API_KEY'}
)
<?php
// Query by address
$address = urlencode('500 Main St Dallas TX 75202');
$apiKey = 'YOUR_API_KEY';
$url = "https://api.doorprofit.com/v1/crime?address={$address}&key=REDACTED";
$response = file_get_contents($url);
$data = json_decode($response, true);
echo $data['crime_score']; // "C+"
print_r($data['incidents']['by_type']); // Counts by crime type
// Or query by coordinates
$url = "https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED";
Neighborhood Endpoint
Get comprehensive neighborhood data including demographics, income, housing, education, cost of living, rent estimates, weather, and natural disaster risk. Accepts either a street address or coordinates.
GET
/v1/neighborhood
Parameters
Parameter
Type
Description
key Required
string
Your API key
Location (provide one of the following):
Option
Parameters
Description
Address
address
Full street address (URL encoded). Example: 500+Main+St+Dallas+TX+75202
Coordinates
lat + lng
Latitude and longitude. Example: lat=32.778635&lng=-96.807295
Example Requests
cURL
JavaScript
Python
PHP
# Query by address
curl "https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED"
# Query by coordinates
curl "https://api.doorprofit.com/v1/neighborhood?lat=32.778635&lng=-96.807295&key=REDACTED"
// Query by address
const response = await fetch(
'https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED'
);
cons
Recorded tool call
{
"type": "tool_call",
"seq": 45,
"ts": "2026-10-18T02:40:17.123Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 30 curl -sSL \"https://api.doorprofit.com/docs/\" -o dp_docs.html && python3 - <<'EOF'\nimport re,html\ns=open('/tmp/dp_docs.html',encoding='utf-8',errors='replace').read()\ns=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)\ns=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)\ns=re.sub(r'<[^>]+>',' ',s)\ns=html.unescape(s)\ns=re.sub(r'[ \\t]+',' ',s)\ns=re.sub(r'\\n\\s*\\n+','\\n',s)\nt=s.strip()\nprint(len(t))\nopen('/tmp/dp_docs.txt','w').write(t)\nprint(t[:6000])\nEOF"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fd105b7e2e76ac5",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 48,
"ts": "2026-10-18T02:40:18.698Z",
"turn": 1,
"output": {
"content": [
{
"text": "45153\nAPI Documentation - DoorProfit API \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n Getting Started \n Introduction \n Authentication \n Quick Start \n API Endpoints \n Crime \n Neighborhood \n Registered Offenders \n Response Data \n Crime Response \n Neighborhood Response \n Offenders Response \n Account \n Usage Statistics \n Embed & Widgets \n Crime Map Widget \n Metering \n Combined Snippet \n AI Agents \n MCP Server \n Code Examples \n All Languages \n Reference \n Error Handling \n Rate Limits \n Support \n API Documentation \n Welcome to the DoorProfit API! Get comprehensive location intelligence data for any US address with our simple REST API. \n We provide three focused endpoints so you can query exactly the data you need. \n Available Endpoints \n /v1/crime - Crime safety scores and nearby incident counts \n /v1/neighborhood - Demographics, income, housing, education, cost of living, rent, weather & disaster risk \n /v1/offenders - Registered registered offenders with photos, addresses, and distances \n /v1/usage - Check your API usage and remaining quota (free, doesn't count against quota) \n The crime, neighborhood, and offenders endpoints accept either a street address or latitude/longitude coordinates. \n Base URL \n https://api.doorprofit.com/v1/ \n Authentication \n All API requests require authentication using your API key. You can pass your key as a query parameter. \n 💡 \n Don't have an API key yet? Sign up for free to get your key instantly. \n Using Your API Key \n Include your API key as the key parameter in every request: \n https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED \n ⚠️ \n Keep your API key secure! Don't expose it in client-side code or public repositories. Use environment variables or server-side requests. \n Quick Start \n Get up and running in under 5 minutes. Here's how to make your first API call: \n Get Your API Key \n Sign up for a free account to get your API key. You'll receive 10 free API calls per month to test the integration. \n Choose Your Endpoint \n Call the endpoint for the data you need: \n # Get crime statistics & safety scores\ncurl \"https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED\"\n# Get neighborhood demographics\ncurl \"https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED\"\n# Get registered offenders in the area\ncurl \"https://api.doorprofit.com/v1/offenders?address=500+Main+St+Dallas+TX+75202&key=REDACTED\" \n Parse the Response \n You'll receive a JSON response with the requested data. That's it - you're ready to build! \n Crime Endpoint \n Get crime statistics, safety scores, crime breakdowns, and recent incident data for any location. Accepts either a street address or coordinates. \n GET \n /v1/crime \n Parameters \n Parameter \n Type \n Description \n key Required \n string \n Your API key \n Location (provide one of the following): \n Option \n Parameters \n Description \n Address \n address \n Full street address (URL encoded). Example: 500+Main+St+Dallas+TX+75202 \n Coordinates \n lat + lng \n Latitude and longitude. Example: lat=32.778635&lng=-96.807295 \n Example Requests \n cURL \n JavaScript \n Python \n PHP \n # Query by address\ncurl \"https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED\"\n# Query by coordinates\ncurl \"https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED\" \n // Query by address\nconst response = await fetch(\n 'https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED'\n);\nconst data = await response.json();\nconsole.log(data.crime_score); // \"C+\"\nconsole.log(data.incidents.count); // Incident count, or null if unavailable\nconsole.log(data.incidents.by_type); // Counts by crime type\n// Or query by coordinates\nconst coordsResponse = await fetch(\n 'https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED'\n); \n import requests\n# Query by address\nresponse = requests.get(\n 'https://api.doorprofit.com/v1/crime',\n params={\n 'address': '500 Main St Dallas TX 75202',\n 'key': 'YOUR_API_KEY'\n }\n)\ndata = response.json()\nprint(data['crime_score']) # \"C+\"\nprint(data['incidents']['by_type']) # Counts by crime type\n# Or query by coordinates\nresponse = requests.get(\n 'https://api.doorprofit.com/v1/crime',\n params={'lat': 32.778635, 'lng': -96.807295, 'key': 'YOUR_API_KEY'}\n) \n <?php\n// Query by address\n$address = urlencode('500 Main St Dallas TX 75202');\n$apiKey = 'YOUR_API_KEY';\n$url = \"https://api.doorprofit.com/v1/crime?address={$address}&key=REDACTED\";\n$response = file_get_contents($url);\n$data = json_decode($response, true);\necho $data['crime_score']; // \"C+\"\nprint_r($data['incidents']['by_type']); // Counts by crime type\n// Or query by coordinates\n$url = \"https://api.doorprofit.com/v1/crime?lat=32.778635&lng=-96.807295&key=REDACTED\"; \n Neighborhood Endpoint \n Get comprehensive neighborhood data including demographics, income, housing, education, cost of living, rent estimates, weather, and natural disaster risk. Accepts either a street address or coordinates. \n GET \n /v1/neighborhood \n Parameters \n Parameter \n Type \n Description \n key Required \n string \n Your API key \n Location (provide one of the following): \n Option \n Parameters \n Description \n Address \n address \n Full street address (URL encoded). Example: 500+Main+St+Dallas+TX+75202 \n Coordinates \n lat + lng \n Latitude and longitude. Example: lat=32.778635&lng=-96.807295 \n Example Requests \n cURL \n JavaScript \n Python \n PHP \n # Query by address\ncurl \"https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED\"\n# Query by coordinates\ncurl \"https://api.doorprofit.com/v1/neighborhood?lat=32.778635&lng=-96.807295&key=REDACTED\" \n // Query by address\nconst response = await fetch(\n 'https://api.doorprofit.com/v1/neighborhood?address=500+Main+St+Dallas+TX+75202&key=REDACTED'\n);\ncons\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fd105b7e2e76ac5",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 30 curl -sSL "https://www.doorprofit.com" -o dp_www.html; grep -oiE '(href|src)="[^"]*(pricing|api|plan|developer)[^"]*"' dp_www.html | sed 's/.*="//;s/"$//' | sort -u | head -30
https://api.doorprofit.com/
https://api.doorprofit.com/ https://fonts.googleapis.com
Recorded tool call
{
"type": "tool_call",
"seq": 46,
"ts": "2026-10-18T02:40:17.123Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 30 curl -sSL \"https://www.doorprofit.com\" -o dp_www.html; grep -oiE '(href|src)=\"[^\"]*(pricing|api|plan|developer)[^\"]*\"' dp_www.html | sed 's/.*=\"//;s/\"$//' | sort -u | head -30"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-87d9409e8c3098de",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 47,
"ts": "2026-10-18T02:40:18.698Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://api.doorprofit.com/\nhttps://fonts.googleapis.com\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-87d9409e8c3098de",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Next, let's check the rest of the documentation, especially pricing/metering/rate limits, AI agents, MCP server, and the usage endpoints. I'll try reading the remaining text.
✓read /tmp/dp_docs.txt✓└{"content":[{"text":" <?php\n$address = urlencode('500 Main St Dallas TX 75202');\n$apiKey = 'YOUR_API_KEY';\n$url = \"https://api.doorprofit.com/v1/neighborhoo…ctrl+o
{"content":[{"text":" <?php\n$address = urlencode('500 Main St Dallas TX 75202');\n$apiKey = 'YOUR_API_KEY';\n$url = \"https://api.doorprofit.com/v1/neighborhood?address={$address}&key=REDACTED\";\n$response = file_get_contents($url);\n$data = json_decode($response, true);\necho $data['neighborhood']['name']; // \"West End Historic District\"\necho $data['neighborhood']['demographics']['population']; // 4280\necho $data['neighborhood']['income']['median']; // 52400\necho $data['neighborhood']['housing']['median_home_value']; // 285000\necho $data['neighborhood']['rent']['median']; // 1150 \n Registered Offenders Endpoint \n Search for registered registered offenders by location or by name. Returns offender details including photos, addresses, offenses, and distances. \n GET \n /v1/offenders \n Parameters \n Parameter \n Type \n Description \n key Required \n string \n Your API key \n Search by Location \n Find offenders near a specific address or coordinates. \n Parameter \n Type \n Description \n address \n string \n Full street address (URL encoded). Example: 500+Main+St+Dallas+TX+75202 \n lat + lng \n number \n Latitude and longitude coordinates. Example: lat=32.778635&lng=-96.807295 \n radius \n number \n Search radius in miles. Default: 1 , Max: 5 \n Search by Name \n Search for offenders by name and/or location details. Use any combination of the parameters below - at least one is required. \n Parameter \n Type \n Description \n first_name \n string \n Offender's first name \n last_name \n string \n Offender's last name \n city \n string \n City name \n state \n string \n Two-letter state code. Example: OK , TX \n zipcode \n string \n 5-digit ZIP code \n Pagination \n Results are paginated. Use these parameters to navigate through large result sets. Each page request counts as one API credit. \n Parameter \n Type \n Description \n page \n integer \n Page number. Default: 1 \n limit \n integer \n Results per page. Default: 50 , Max: 100 \n Example Requests \n cURL \n JavaScript \n Python \n PHP \n # Search by address (default 1 mile radius)\ncurl \"https://api.doorprofit.com/v1/offenders?address=500+Main+St+Dallas+TX+75202&key=REDACTED\"\n# Search by coordinates with 2 mile radius\ncurl \"https://api.doorprofit.com/v1/offenders?lat=32.778635&lng=-96.807295&radius=2&key=REDACTED\"\n# Search by name\ncurl \"https://api.doorprofit.com/v1/offenders?last_name=Smith&state=TX&key=REDACTED\"\n# Search by name and city\ncurl \"https://api.doorprofit.com/v1/offenders?first_name=John&city=Dallas&state=TX&key=REDACTED\" \n // Search by address\nconst response = await fetch(\n 'https://api.doorprofit.com/v1/offenders?address=500+Main+St+Dallas+TX+75202&key=REDACTED'\n);\nconst data = await response.json();\nconsole.log(data.offenders); // Array of offenders\nconsole.log(data.offenders_count); // Total count\n// Search by name\nconst nameSearch = await fetch(\n 'https://api.doorprofit.com/v1/offenders?last_name=Smith&state=TX&key=REDACTED'\n);\nconst nameResults = await nameSearch.json();\n// Search with multiple criteria\nconst params = new URLSearchParams({\n first_name: 'John',\n city: 'Dallas',\n state: 'TX',\n key: 'YOUR_API_KEY'\n});\nconst multiSearch = await fetch(`https://api.doorprofit.com/v1/offenders?${params}`); \n import requests\n# Search by address\nresponse = requests.get(\n 'https://api.doorprofit.com/v1/offenders',\n params={\n 'address': '500 Main St Dallas TX 75202',\n 'key': 'YOUR_API_KEY'\n }\n)\ndata = response.json()\nprint(f\"Found {data['offenders_count']} offenders nearby\")\n# Search by name\nresponse = requests.get(\n 'https://api.doorprofit.com/v1/offenders',\n params={\n 'last_name': 'Smith',\n 'state': 'TX',\n 'key': 'YOUR_API_KEY'\n }\n)\n# Search with multiple criteria\nresponse = requests.get(\n 'https://api.doorprofit.com/v1/offenders',\n params={\n 'first_name': 'John',\n 'city': 'Dallas',\n 'state': 'TX',\n 'key': 'YOUR_API_KEY'\n }\n) \n <?php\n$apiKey = 'YOUR_API_KEY';\n// Search by address\n$address = urlencode('500 Main St Dallas TX 75202');\n$url = \"https://api.doorprofit.com/v1/offenders?address={$address}&key=REDACTED\";\n$data = json_decode(file_get_contents($url), true);\necho \"Found \" . $data['offenders_count'] . \" offenders nearby\\n\";\n// Search by name\n$url = \"https://api.doorprofit.com/v1/offenders?last_name=Smith&state=TX&key=REDACTED\";\n$data = json_decode(file_get_contents($url), true);\n// Search with multiple criteria\n$params = http_build_query([\n 'first_name' => 'John',\n 'city' => 'Dallas',\n 'state' => 'TX',\n 'key' => $apiKey\n]);\n$url = \"https://api.doorprofit.com/v1/offenders?{$params}\"; \n Usage Statistics Endpoint \n Check your current API usage, remaining quota, and subscription details. This endpoint is free and does not count against your API quota. \n GET \n /v1/usage \n Parameters \n Parameter \n Type \n Description \n key Required \n string \n Your API key \n Example Request \n curl \"https://api.doorprofit.com/v1/usage?key=REDACTED\" \n Example Response \n {\n \"success\": true,\n \"data\": {\n \"plan\": {\n \"type\": \"growth\",\n \"name\": \"Growth\",\n \"price_monthly\": 50\n },\n \"monthly\": {\n \"limit\": 600,\n \"used\": 127,\n \"remaining\": 473,\n \"percentage_used\": 21.2,\n \"resets_at\": \"2026-03-01T00:00:00+00:00\"\n },\n \"daily\": {\n \"limit\": 3000,\n \"used\": 45,\n \"remaining\": 2955,\n \"percentage_used\": 1.5,\n \"resets_at\": \"2026-02-03T00:00:00+00:00\"\n },\n \"rate_limit\": {\n \"requests_per_second\": 10\n },\n \"overage\": {\n \"enabled\": true,\n \"calls\": 0,\n \"amount_due\": \"0.00\",\n \"rate_per_call\": \"0.10\"\n },\n \"account\": {\n \"status\": \"active\",\n \"subscription_status\": \"active\",\n \"blocked\": false\n }\n },\n \"meta\": {\n \"note\": \"This endpoint does not count against your API quota.\",\n \"timestamp\": \"2026-02-02T15:30:00+00:00\"\n }\n} \n Crime Response \n The /v1/crime endpoint returns a safety grade and, in cities that publish police incidents, counts of those incidents near the point. \n Response Fields \n crime_score string \n Letter grade from A+ (safest) to F (least safe). In cities with incident-level coverage the grade is computed from actual reported incidents at the census-block level; elsewhere it falls back to block-group statistics. Example: \"C+\" \n crime_numeric number \n Numeric crime score from 0 to 1. Lower is safer. Example: 0.21 \n crime_score_source string \n \"incidents\" when the grade is derived from incident-level data for that census block, \"block_group\" when it comes from aggregated block-group statistics \n crime_description string \n Human-readable description of the crime level. Example: \"Higher than average\" \n incidents object | null \n Counts within 1,000 feet for the period the city's grade was built from ( date_from through date_to , which varies by city). by_type always includes all 12 types the grade uses; 0 means none were reported. null where we do not have incident data for that city. There is no list of individual incidents. \n Example Response \n {\n \"success\": true,\n \"location\": {\n \"address\": \"500 Main St, Dallas, TX 75202, USA\",\n \"city\": \"Dallas\",\n \"state\": \"TX\",\n \"zipcode\": \"75202\",\n \"county\": \"Dallas County\",\n \"lat\": 32.78014,\n \"lng\": -96.800454\n },\n \"crime_score\": \"C+\",\n \"crime_numeric\": 0.21,\n \"crime_score_source\": \"incidents\",\n \"crime_description\": \"Higher than average crime\",\n \"incidents\": {\n \"radius_feet\": 1000,\n \"date_from\": \"2025-04-01\",\n \"date_to\": \"2026-04-01\",\n \"count\": 245,\n \"by_type\": {\n \"murder\": 0,\n \"shooting\": 3,\n \"assault\": 74,\n \"sexual_offense\": 7,\n \"robbery\": 5,\n \"kidnapping\": 1,\n \"weapons\": 1,\n \"theft\": 109,\n \"burglary\": 5,\n \"drug_offense\": 1,\n \"vandalism\": 39,\n \"arson\": 0\n }\n }\n} \n Neighborhood Response \n The /v1/neighborhood endpoint returns comprehensive demographic, economic, housing, education, cost of living, rent, and weather data for the neighborhood at the given location. \n Top-Level Fields \n neighborhood.name string \n Name of the neighborhood \n neighborhood.type string \n \"neighborhood\" or \"block_group\" (fallback when no named neighborhood) \n Demographics \n demographics.population number \n Current population \n demographics.population_density number \n People per square mile \n demographics.population_growth_pct number \n Population growth over last 10 years (%) \n demographics.median_age number \n Median age of residents \n demographics.gender object \n Gender split: male_pct , female_pct \n demographics.race object \n Racial demographics: white_pct , black_pct , asian_pct , hispanic_pct , native_pct \n demographics.age_distribution object \n Age brackets: 0_5 , 6_11 , 12_17 , 18_24 , 25_34 , 35_44 , 45_54 , 55_64 , 65_74 , 75_84 , 85_plus (all %) \n Income & Housing \n income.median number \n Median household income (USD) \n income.average number \n Average household income (USD) \n income.distribution object \n Income brackets: under_15k through 200k_plus (all %) \n housing object \n median_home_value , total_households , avg_year_built , plus occupancy (owner/renter/vacant %) and household_types (families/married/children %) \n Education, Cost of Living & Rent \n education object \n Attainment rates: high_school_pct , associates_pct , bachelors_pct , graduate_pct \n cost_of_living object \n Index values (100 = national avg): overall_index , housing , food , healthcare , transportation , utilities , apparel , education , entertainment \n rent object \n median , average , and by_bedroom ( 1br , 2br , 3br , 4br ) \n Weather \n weather.temperatures object \n Seasonal highs/lows in °F: annual_high , annual_low , january_high , july_high , etc. \n weather.precipitation object \n rain_inches , rain_days_per_year , snow_inches , snow_days_per_year \n weather.natural_disaster_risk object \n Risk indices (100 = national avg): overall_index , earthquake , hail , hurricane , tornado , wind \n Example Response \n {\n \"success\": true,\n \"location\": {\n \"address\": \"500 Main St, Dallas, TX 75202, USA\",\n \"city\": \"Dallas\",\n \"state\": \"TX\",\n \"zipcode\": \"75202\",\n \"county\": \"Dallas County\",\n \"lat\": 32.78014,\n \"lng\": -96.800454\n },\n \"neighborhood\": {\n \"name\": \"West End Historic District\",\n \"type\": \"neighborhood\",\n \"demographics\": {\n \"population\": 4280,\n \"population_density\": 1200.5,\n \"population_growth_pct\": 3.5,\n \"median_age\": 34.2,\n \"gender\": { \"male_pct\": 51.2, \"female_pct\": 48.8 },\n \"race\": {\n \"white_pct\": 45.2,\n \"black_pct\": 28.3,\n \"asian_pct\": 5.1,\n \"hispanic_pct\": 18.7,\n \"native_pct\": 0.8\n },\n \"age_distribution\": {\n \"0_5\": 6.2, \"6_11\": 5.8, \"12_17\": 4.9,\n \"18_24\": 12.1, \"25_34\": 22.5, \"35_44\": 18.3,\n\n[791 more lines in file. Use offset=530 to continue.]","type":"text"}]}grep -niE 'price|pricing|\$[0-9]|per month|/mo|free tier|quota|rate limit|plan' /tmp/dp_docs.txt | head -60
38: Rate Limits
38: Rate Limits
47: /v1/usage - Check your API usage and remaining quota (free, doesn't count against quota)
63: Sign up for a free account to get your API key. You'll receive 10 free API calls per month to test the integration.
349: Check your current API usage, remaining quota, and subscription details. This endpoint is free and does not count against your API quota.
365: "plan": {
368: "price_monthly": 50
400: "note": "This endpoint does not count against your API quota.",
745: The crime map widget uses a separate "embed views" quota from the regular API call quota. This keeps embeds cheap so you can put them on as many pages as you like. See the pricing page for plan limits and overage rates.
748: Known crawlers and bots (Googlebot, Bingbot, AhrefsBot, social previewers, etc.) are automatically excluded and never count toward your quota
776: Quota-friendly: Widget page-loads count against the separate embed views bucket , not your API calls. Geocodes are cached, so most embed loads cost zero.
785: get_api_usage — your plan, usage, and remaining quota (free)
801: Quotas & Billing
802: MCP tool calls are metered exactly like REST calls — each data tool call uses 1 API call from your plan's monthly and daily quota.
803: When the quota runs out, the AI receives the standard QUOTA_EXCEEDED error and will let you know it's time to upgrade .
804: The test address 500 Main St, Dallas, TX 75202 never consumes quota — perfect for trying the integration.
1247: Too Many Requests - Rate limit exceeded
1265: QUOTA_EXCEEDED - Monthly API call limit reached
1266: Rate Limits
1267: To ensure fair usage and maintain service quality, the API enforces rate limits based on your subscription plan. See the pricing page for plan details and pricing.
1268: Plan
1271: Rate Limit
1273: 10 calls/month
1277: 300 calls/month
1281: 600 calls/month
1285: 1,200 calls/month
1293: Use the /v1/usage endpoint to check your current usage without consuming quota:
1302: X-Plan - Your current subscription plan
1309: Response Time: Within 24 hours for all plans, priority support for paid plans
1316: Yes! All paid plans include commercial usage rights.
1318: API calls beyond your limit will return a 429 error. You can upgrade your plan anytime to get more calls.
Recorded tool call
{
"type": "tool_call",
"seq": 52,
"ts": "2026-10-18T02:40:21.165Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -niE 'price|pricing|\\$[0-9]|per month|/mo|free tier|quota|rate limit|plan' /tmp/dp_docs.txt | head -60"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81e4d2ffbc6169f3",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 54,
"ts": "2026-10-18T02:40:21.165Z",
"turn": 1,
"output": {
"content": [
{
"text": "38: Rate Limits \n47: /v1/usage - Check your API usage and remaining quota (free, doesn't count against quota) \n63: Sign up for a free account to get your API key. You'll receive 10 free API calls per month to test the integration. \n349: Check your current API usage, remaining quota, and subscription details. This endpoint is free and does not count against your API quota. \n365: \"plan\": {\n368: \"price_monthly\": 50\n400: \"note\": \"This endpoint does not count against your API quota.\",\n745: The crime map widget uses a separate \"embed views\" quota from the regular API call quota. This keeps embeds cheap so you can put them on as many pages as you like. See the pricing page for plan limits and overage rates. \n748: Known crawlers and bots (Googlebot, Bingbot, AhrefsBot, social previewers, etc.) are automatically excluded and never count toward your quota \n776: Quota-friendly: Widget page-loads count against the separate embed views bucket , not your API calls. Geocodes are cached, so most embed loads cost zero. \n785: get_api_usage — your plan, usage, and remaining quota (free) \n801: Quotas & Billing \n802: MCP tool calls are metered exactly like REST calls — each data tool call uses 1 API call from your plan's monthly and daily quota. \n803: When the quota runs out, the AI receives the standard QUOTA_EXCEEDED error and will let you know it's time to upgrade . \n804: The test address 500 Main St, Dallas, TX 75202 never consumes quota — perfect for trying the integration. \n1247: Too Many Requests - Rate limit exceeded \n1265: QUOTA_EXCEEDED - Monthly API call limit reached \n1266: Rate Limits \n1267: To ensure fair usage and maintain service quality, the API enforces rate limits based on your subscription plan. See the pricing page for plan details and pricing. \n1268: Plan \n1271: Rate Limit \n1273: 10 calls/month \n1277: 300 calls/month \n1281: 600 calls/month \n1285: 1,200 calls/month \n1293: Use the /v1/usage endpoint to check your current usage without consuming quota: \n1302: X-Plan - Your current subscription plan \n1309: Response Time: Within 24 hours for all plans, priority support for paid plans \n1316: Yes! All paid plans include commercial usage rights. \n1318: API calls beyond your limit will return a 429 error. You can upgrade your plan anytime to get more calls. \n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81e4d2ffbc6169f3",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Let me try reading the rate limits/plans table, the AI Agents/MCP section, and the pricing page. The documentation mentions a "pricing page," but api.doorprofit.com/pricing returned a 404. It's probably either www.doorprofit.com/api or www.doorprofit.com/pricing. Let me check.
✓read /tmp/dp_docs.txt✓└{"content":[{"text":" 401 \n Unauthorized - Invalid or missing API key \n 403 \n Forbidden - API key doesn't have access to this resource \n 404 \n Not Found - …ctrl+o
{"content":[{"text":" 401 \n Unauthorized - Invalid or missing API key \n 403 \n Forbidden - API key doesn't have access to this resource \n 404 \n Not Found - Address could not be geocoded \n 429 \n Too Many Requests - Rate limit exceeded \n 500 \n Server Error - Something went wrong on our end \n Error Response Format \n {\n \"success\": false,\n \"error\": {\n \"code\": \"INVALID_API_KEY\",\n \"message\": \"The API key provided is invalid or has been revoked.\",\n \"documentation_url\": \"https://api.doorprofit.com/docs/#authentication\"\n }\n} \n Common Error Codes \n INVALID_API_KEY - API key is missing, invalid, or revoked \n MISSING_PARAMETER - Required parameter is missing \n INVALID_ADDRESS - Address could not be parsed or geocoded \n INVALID_COORDINATES - Lat/lng values are out of range \n RATE_LIMIT_EXCEEDED - Too many requests, slow down \n QUOTA_EXCEEDED - Monthly API call limit reached \n Rate Limits \n To ensure fair usage and maintain service quality, the API enforces rate limits based on your subscription plan. See the pricing page for plan details and pricing. \n Plan \n Monthly Calls \n Daily Limit \n Rate Limit \n Free \n 10 calls/month \n 50/day \n 2 req/sec \n Starter \n 300 calls/month \n 1,500/day \n 5 req/sec \n Growth \n 600 calls/month \n 3,000/day \n 10 req/sec \n Pro \n 1,200 calls/month \n 6,000/day \n 20 req/sec \n Enterprise \n Custom \n Custom \n Custom \n Checking Your Usage \n Use the /v1/usage endpoint to check your current usage without consuming quota: \n curl \"https://api.doorprofit.com/v1/usage?key=REDACTED\" \n Response Headers \n Every API response includes headers to help you track usage: \n X-Monthly-Limit - Your monthly API call limit \n X-Monthly-Remaining - API calls remaining this month \n X-Daily-Limit - Your daily API call limit \n X-Daily-Remaining - API calls remaining today \n X-RateLimit-Limit - Requests allowed per second \n X-Plan - Your current subscription plan \n 💡 \n Pro tip: Cache API responses when possible to reduce calls and improve your app's performance. \n Support \n Need help? We're here for you. \n Contact Us \n Email: support@doorprofit.com \n Response Time: Within 24 hours for all plans, priority support for paid plans \n Common Questions \n Q: How often is the data updated? \n Crime data is updated daily. Registered offender data is refreshed weekly. Demographic data follows US Census updates. \n Q: What areas are covered? \n We cover all 50 US states with data for most metropolitan and suburban areas. \n Q: Can I use the API for commercial purposes? \n Yes! All paid plans include commercial usage rights. \n Q: What happens if I exceed my monthly limit? \n API calls beyond your limit will return a 429 error. You can upgrade your plan anytime to get more calls. \n 🚀 \n Ready to get started? Sign up for free and make your first API call in minutes!","type":"text"}]}for u in "https://www.doorprofit.com/pricing" "https://www.doorprofit.com/pricing/" "https://www.doorprofit.com/api" "https://www.doorprofit.com/api/" "https://www.doorprofit.com/api-pricing" "https://www.doorprofit.com/api/pricing"; do printf "%-48s " "$u"; timeout 15 curl -sSL -o /tmp/p.html -w "%{http_code} %{size_download}\n" "$u" 2>&1|tail -1; donehttps://www.doorprofit.com/pricing 404 26708
https://www.doorprofit.com/pricing 404 26708 https://www.doorprofit.com/pricing/ 404 26709 https://www.doorprofit.com/api 200 0 https://www.doorprofit.com/api/ 200 0 https://www.doorprofit.com/api-pricing 404 26712 https://www.doorprofit.com/api/pricing 404 26712
Recorded tool call
{
"type": "tool_call",
"seq": 58,
"ts": "2026-10-18T02:40:24.766Z",
"name": "bash",
"turn": 1,
"input": {
"command": "for u in \"https://www.doorprofit.com/pricing\" \"https://www.doorprofit.com/pricing/\" \"https://www.doorprofit.com/api\" \"https://www.doorprofit.com/api/\" \"https://www.doorprofit.com/api-pricing\" \"https://www.doorprofit.com/api/pricing\"; do printf \"%-48s \" \"$u\"; timeout 15 curl -sSL -o /tmp/p.html -w \"%{http_code} %{size_download}\\n\" \"$u\" 2>&1|tail -1; done"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b8094940f477c550",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 60,
"ts": "2026-10-18T02:40:27.594Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://www.doorprofit.com/pricing 404 26708\nhttps://www.doorprofit.com/pricing/ 404 26709\nhttps://www.doorprofit.com/api 200 0\nhttps://www.doorprofit.com/api/ 200 0\nhttps://www.doorprofit.com/api-pricing 404 26712\nhttps://www.doorprofit.com/api/pricing 404 26712\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b8094940f477c550",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The pricing page isn't easy to find. Let me check the AI agents / MCP section of the documentation (lines 745–830), and look for the pricing page link from the docs HTML (it says "refer to the pricing page"). I'll grep for "pricing" links within the docs HTML.
Also, I'll check the /signup page and the sitemap.
cd /tmp && grep -oiE 'href="[^"]*pricing[^"]*"' dp_docs.html dp_www.html dp_api.html | sort -u; echo "=== sitemap ==="; timeout 20 curl -sSL "https://api.doorprofit.com/sitemap.xml" 2>&1 | head -c 1500; echo; echo "=== www sitemap ==="; timeout 20 curl -sSL "https://www.doorprofit.com/sitemap.xml" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -40
=== sitemap ===
=== sitemap ===
<!DOCTYPE html>
<html>
<head>
<title>404 - Not Found</title>
<style>
body { font-family: -apple-system, sans-serif; background: #ffffff; color: #0f172a; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
.container { text-align: center; }
h1 { font-size: 72px; margin: 0; color: #4f46e5; }
p { color: #475569; margin: 16px 0 24px; }
a { color: #4f46e5; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>404</h1>
<p>This page doesn't exist.</p>
<a href="/">Back to DoorProfit API</a>
</div>
</body>
</html>
=== www sitemap ===
https://www.doorprofit.com/sitemaps/sitemap-index.xml
Recorded tool call
{
"type": "tool_call",
"seq": 63,
"ts": "2026-10-18T02:40:30.591Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oiE 'href=\"[^\"]*pricing[^\"]*\"' dp_docs.html dp_www.html dp_api.html | sort -u; echo \"=== sitemap ===\"; timeout 20 curl -sSL \"https://api.doorprofit.com/sitemap.xml\" 2>&1 | head -c 1500; echo; echo \"=== www sitemap ===\"; timeout 20 curl -sSL \"https://www.doorprofit.com/sitemap.xml\" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -40"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9accd717028c358b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 66,
"ts": "2026-10-18T02:40:31.197Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== sitemap ===\n<!DOCTYPE html>\n <html>\n <head>\n <title>404 - Not Found</title>\n <style>\n body { font-family: -apple-system, sans-serif; background: #ffffff; color: #0f172a; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }\n .container { text-align: center; }\n h1 { font-size: 72px; margin: 0; color: #4f46e5; }\n p { color: #475569; margin: 16px 0 24px; }\n a { color: #4f46e5; text-decoration: none; }\n a:hover { text-decoration: underline; }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>404</h1>\n <p>This page doesn't exist.</p>\n <a href=\"/\">Back to DoorProfit API</a>\n </div>\n </body>\n </html>\n=== www sitemap ===\nhttps://www.doorprofit.com/sitemaps/sitemap-index.xml\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9accd717028c358b",
"is_error": false,
"tool_kind": "shell"
}✓read /tmp/dp_docs.txt✓└{"content":[{"text":" data-zoom No Initial zoom level (default: 13) \n data-height No Map height (CSS value, default: \"400px\") \n * Provide either data-addres…ctrl+o
{"content":[{"text":" data-zoom No Initial zoom level (default: 13) \n data-height No Map height (CSS value, default: \"400px\") \n * Provide either data-address or both data-lat and data-lng . \n How It Works \n The widget loads MapLibre GL (open-source, no Mapbox token needed) and renders a map with an OpenFreeMap Positron basemap \n The widget calls /v1/crime to validate your API key and resolve the location. If using data-address , the server geocodes it; data-lat / data-lng skips geocoding but still validates the key. \n Block-level crime heatmap data loads from MVT vector tiles as the user pans the map (no additional API calls) \n A visible \"Crime data by DoorProfit\" attribution link is required and cannot be removed \n Attribution Required: The \"Crime data by DoorProfit\" link must remain visible. Removing it will disable the map. This is the same model used by Mapbox, CARTO, and OpenStreetMap.\n Embed Metering & Domain Restrictions \n The crime map widget uses a separate \"embed views\" quota from the regular API call quota. This keeps embeds cheap so you can put them on as many pages as you like. See the pricing page for plan limits and overage rates. \n How a view is counted \n Each unique visitor (browser) counts as 1 embed view per calendar month per API key — the same visitor returning all month is still just 1 view \n Known crawlers and bots (Googlebot, Bingbot, AhrefsBot, social previewers, etc.) are automatically excluded and never count toward your quota \n Map tile requests (20–100 per page-load) are free \n Geocoded addresses are cached server-side, so the same address on any number of pages costs zero API calls after the first lookup \n A page with multiple widgets on it counts as one view , not one per widget \n Response headers \n Every response from /v1/embed/beacon includes usage headers so your integration can self-monitor: \n X-Embed-Views-Limit — your monthly cap \n X-Embed-Views-Used — views counted so far this period \n X-Embed-Views-Remaining — views still available before overage \n Restricting your key to certain domains \n By default an API key works from any site. If you want to restrict it (so a stolen key can't be used to render embeds on other domains), go to your dashboard and fill in the Allowed domains field. One hostname per line; prefix with a dot ( .example.com ) to match all subdomains. Embed requests from outside the allowlist are rejected with HTTP 403. \n Widget + noscript Fallback (Recommended) \n For maximum SEO value, combine the interactive map with a <noscript> text link fallback. JavaScript-capable browsers get the full map; crawlers and non-JS environments see a plain HTML backlink. \n HTML \n <!-- Interactive Crime Map -->\n<div id=\"doorprofit-crime-map\"\n data-address=\"500 Adams St, Tulsa, OK\"\n data-key=\"YOUR_API_KEY\"></div>\n<script src=\"https://api.doorprofit.com/v1/embed/crime-map.js\" async></script>\n<!-- Fallback for non-JS (guaranteed crawlable backlink) -->\n<noscript>\n <a href=\"https://www.doorprofit.com//crime-map/\" target=\"_blank\" rel=\"noopener\">\n View crime data for this area on DoorProfit\n </a>\n</noscript> \n Why Use Both? \n Interactive map: Gives your visitors an engaging, interactive crime heatmap they'll actually use \n noscript link: Guarantees the backlink is visible to every search engine crawler, even those that don't render JavaScript \n Quota-friendly: Widget page-loads count against the separate embed views bucket , not your API calls. Geocodes are cached, so most embed loads cost zero. \n MCP Server (Claude, ChatGPT, Cursor & AI Agents) \n DoorProfit ships a hosted MCP server (Model Context Protocol) so AI assistants and agents can query crime, neighborhood, and offender data directly in conversation — no code required. Ask Claude \"Is 500 Adams St in Tulsa safe?\" and it calls the DoorProfit API for real data. \n Server URL \n https://api.doorprofit.com/mcp \n Available Tools \n get_crime_data — safety score and nearby incident counts for an address (uses 1 API call) \n get_neighborhood_data — demographics, income, housing, rent, weather & disaster risk (uses 1 API call) \n search_offenders — registered offenders near a location or by name (uses 1 API call) \n get_api_usage — your plan, usage, and remaining quota (free) \n Connecting \n Option 1 — OAuth (easiest): add the server URL to any OAuth-capable MCP client (Claude, ChatGPT, Cursor). You'll be redirected to log in to your DoorProfit account and approve access — no key copying needed. \n Option 2 — API key: configure the server with your API key as a bearer header: \n MCP client config (JSON) \n {\n \"mcpServers\": {\n \"doorprofit\": {\n \"url\": \"https://api.doorprofit.com/mcp\",\n \"headers\": {\n \"Authorization\": \"Bearer YOUR_API_KEY\"\n }\n }\n }\n} \n Clients that only accept a URL can append the key instead: https://api.doorprofit.com/mcp?key=REDACTED \n Quotas & Billing \n MCP tool calls are metered exactly like REST calls — each data tool call uses 1 API call from your plan's monthly and daily quota. \n When the quota runs out, the AI receives the standard QUOTA_EXCEEDED error and will let you know it's time to upgrade . \n The test address 500 Main St, Dallas, TX 75202 never consumes quota — perfect for trying the integration. \n Complete Code Examples \n Full working examples showing how to use both API endpoints in popular programming languages. \n JavaScript / Node.js \n JavaScript \n // DoorProfit API - JavaScript Example\nconst API_KEY = 'YOUR_API_KEY';\nconst BASE_URL = 'https://api.doorprofit.com/v1';\n// Get crime data\nasync function getCrimeData(address) {\n const url = `${BASE_URL}/crime?address=${encodeURIComponent(address)}&key=REDACTED`;\n const response = await fetch(url);\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n return response.json();\n}\n// Get neighborhood demographics\nasync function getNeighborhood(address) {\n const url = `${BASE_URL}/neighborhood?address=${encodeURIComponent(address)}&key=REDACTED`;\n const response = await fetch(url);\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n return response.json();\n}\n// Get registered offenders\nasync function getOffenders(address, radius = 1) {\n const url = `${BASE_URL}/offenders?address=${encodeURIComponent(address)}&radius=${radius}&key=REDACTED`;\n const response = await fetch(url);\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n return response.json();\n}\n// Example usage\nasync function main() {\n try {\n const address = '500 Main St, Dallas, TX 75202';\n // Get crime data\n const crimeData = await getCrimeData(address);\n console.log('Crime Score:', crimeData.crime_score);\n // Get neighborhood demographics (separate endpoint)\n const neighborhoodData = await getNeighborhood(address);\n console.log('Median Income:', neighborhoodData.neighborhood.income.median);\n console.log('Population:', neighborhoodData.neighborhood.demographics.population);\n console.log('Median Rent:', neighborhoodData.neighborhood.rent?.median);\n\n[476 more lines in file. Use offset=845 to continue.]","type":"text"}]}›Thinking…
Key findings so far:
- DoorProfit is a real hosted REST API: https://api.doorprofit.com/v1/{crime,neighborhood,offenders,usage}
- Auth:
keyquery parameter. No SDK on npm/PyPI (theimport DoorProfit from 'doorprofit'on the homepage is marketing pseudocode — there's no such package on npm; should double-check). - Free tier: 10 calls/month, 50 calls/day, 2 req/sec. Paid plans are Starter/Growth/Pro/Enterprise.
- Important: the test address "500 Main St, Dallas, TX 75202" does not consume quota at all. That means I can build and run an example without an API key… but wait, is a key even required? It says key Required. Let me test with a dummy key against the test address — the docs say it doesn't consume quota, but does it require a valid key? Let's actually try it.
- MCP server: hosted at https://api.doorprofit.com/mcp — this is an agent-friendly workflow that I can actually act on! It's relevant to "will it be easy for me (the agent) to manage."
Next, let me hunt down pricing info. The docs mention a "pricing page" twice, but /pricing on api is a 404. Let me check the sitemap index on www, and look at the /signup and /login pages. I'll also check the docs nav links in dp_docs.html.
Let me also actually test the API.
Let me make the calls in parallel.
timeout 25 curl -sSL "https://www.doorprofit.com/sitemaps/sitemap-index.xml" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -30
https://www.doorprofit.com/sitemaps/sitemap-static.xml
https://www.doorprofit.com/sitemaps/sitemap-static.xml https://www.doorprofit.com/sitemaps/sitemap-states.xml https://www.doorprofit.com/sitemaps/sitemap-cities-al.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ak.xml https://www.doorprofit.com/sitemaps/sitemap-cities-az.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ar.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ca.xml https://www.doorprofit.com/sitemaps/sitemap-cities-co.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ct.xml https://www.doorprofit.com/sitemaps/sitemap-cities-de.xml https://www.doorprofit.com/sitemaps/sitemap-cities-fl.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ga.xml https://www.doorprofit.com/sitemaps/sitemap-cities-hi.xml https://www.doorprofit.com/sitemaps/sitemap-cities-id.xml https://www.doorprofit.com/sitemaps/sitemap-cities-il.xml https://www.doorprofit.com/sitemaps/sitemap-cities-in.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ia.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ks.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ky.xml https://www.doorprofit.com/sitemaps/sitemap-cities-la.xml https://www.doorprofit.com/sitemaps/sitemap-cities-me.xml https://www.doorprofit.com/sitemaps/sitemap-cities-md.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ma.xml https://www.doorprofit.com/sitemaps/sitemap-cities-mi.xml https://www.doorprofit.com/sitemaps/sitemap-cities-mn.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ms.xml https://www.doorprofit.com/sitemaps/sitemap-cities-mo.xml https://www.doorprofit.com/sitemaps/sitemap-cities-mt.xml https://www.doorprofit.com/sitemaps/sitemap-cities-ne.xml https://www.doorprofit.com/sitemaps/sitemap-cities-nv.xml
Recorded tool call
{
"type": "tool_call",
"seq": 69,
"ts": "2026-10-18T02:40:39.253Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 25 curl -sSL \"https://www.doorprofit.com/sitemaps/sitemap-index.xml\" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -30"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9dfc11f5e056fcd4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 71,
"ts": "2026-10-18T02:40:39.867Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://www.doorprofit.com/sitemaps/sitemap-static.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-states.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-al.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ak.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-az.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ar.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ca.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-co.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ct.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-de.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-fl.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ga.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-hi.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-id.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-il.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-in.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ia.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ks.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ky.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-la.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-me.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-md.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ma.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-mi.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-mn.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ms.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-mo.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-mt.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-ne.xml\nhttps://www.doorprofit.com/sitemaps/sitemap-cities-nv.xml\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9dfc11f5e056fcd4",
"is_error": false,
"tool_kind": "shell"
}echo "=== TEST ADDR, no key ==="; timeout 25 curl -sS -w "\nHTTP:%{http_code}\n" "https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202" 2>&1|head -c 1200; echo; echo "=== TEST ADDR, dummy key ==="; timeout 25 curl -sS -w "\nHTTP:%{http_code}\n" "https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED" 2>&1|head -c 1500=== TEST ADDR, no key ===
=== TEST ADDR, no key ===
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "The API key provided is invalid or has been revoked.",
"documentation_url": "https://api.doorprofit.com/docs/#authentication"
}
}
HTTP:401
=== TEST ADDR, dummy key ===
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "The API key provided is invalid or has been revoked.",
"documentation_url": "https://api.doorprofit.com/docs/#authentication"
}
}
HTTP:401
Recorded tool call
{
"type": "tool_call",
"seq": 70,
"ts": "2026-10-18T02:40:39.253Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== TEST ADDR, no key ===\"; timeout 25 curl -sS -w \"\\nHTTP:%{http_code}\\n\" \"https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202\" 2>&1|head -c 1200; echo; echo \"=== TEST ADDR, dummy key ===\"; timeout 25 curl -sS -w \"\\nHTTP:%{http_code}\\n\" \"https://api.doorprofit.com/v1/crime?address=500+Main+St+Dallas+TX+75202&key=REDACTED\" 2>&1|head -c 1500"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9123b965c4201960",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 72,
"ts": "2026-10-18T02:40:39.867Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== TEST ADDR, no key ===\n{\n \"success\": false,\n \"error\": {\n \"code\": \"INVALID_API_KEY\",\n \"message\": \"The API key provided is invalid or has been revoked.\",\n \"documentation_url\": \"https://api.doorprofit.com/docs/#authentication\"\n }\n}\nHTTP:401\n\n=== TEST ADDR, dummy key ===\n{\n \"success\": false,\n \"error\": {\n \"code\": \"INVALID_API_KEY\",\n \"message\": \"The API key provided is invalid or has been revoked.\",\n \"documentation_url\": \"https://api.doorprofit.com/docs/#authentication\"\n }\n}\nHTTP:401\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9123b965c4201960",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
A real API key is required. Signing up requires creating an account (email/password, probably via a web form). Can I get a key programmatically? Let me check the signup page — maybe there's an API endpoint for signups. However, creating an account requires an email address and probably email verification, which I can't do (no mailbox available). This is a potential blocker, but the instructions say "use the hosted product through its SDK or API" — so I should still build a sample and note that a real key is required.
Let me check the signup page and see if a simple programmatic signup is possible.
Also, I still need pricing info. Let me look for a pricing page in sitemap-static.xml.
timeout 25 curl -sSL "https://www.doorprofit.com/sitemaps/sitemap-static.xml" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -40
https://www.doorprofit.com/
https://www.doorprofit.com/ https://www.doorprofit.com/crime-map/ https://www.doorprofit.com/crime-statistics/ https://www.doorprofit.com/login/ https://www.doorprofit.com/signup/ https://www.doorprofit.com/deal-analyzer/ https://www.doorprofit.com/investment-deals/ https://www.doorprofit.com/terms/ https://www.doorprofit.com/privacy-policy/ https://www.doorprofit.com/forgot-password/
Recorded tool call
{
"type": "tool_call",
"seq": 75,
"ts": "2026-10-18T02:40:43.792Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 25 curl -sSL \"https://www.doorprofit.com/sitemaps/sitemap-static.xml\" 2>&1 | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | head -40"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bbde429deccaf288",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 78,
"ts": "2026-10-18T02:40:44.967Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://www.doorprofit.com/\nhttps://www.doorprofit.com/crime-map/\nhttps://www.doorprofit.com/crime-statistics/\nhttps://www.doorprofit.com/login/\nhttps://www.doorprofit.com/signup/\nhttps://www.doorprofit.com/deal-analyzer/\nhttps://www.doorprofit.com/investment-deals/\nhttps://www.doorprofit.com/terms/\nhttps://www.doorprofit.com/privacy-policy/\nhttps://www.doorprofit.com/forgot-password/\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bbde429deccaf288",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 25 curl -sSL "https://api.doorprofit.com/signup/" -o dp_signup.html -w "HTTP:%{http_code} size:%{size_download}\n"; grep -oE '<(input|form|button)[^>]*>' dp_signup.html | head -30HTTP:200 size:33049
HTTP:200 size:33049 <button class="mobile-menu-toggle" aria-label="Open menu"> <button class="mobile-menu-close" aria-label="Close menu"> <form id="apiSignupForm"> <input type="email" id="email" name="email" required placeholder="Enter your email"> <button type="button" id="continueBtn" class="btn btn-primary btn-lg form-submit"> <button type="button" id="changeEmailBtn" class="change-email-btn"> <input type="text" id="firstName" name="first_name" placeholder="Jane"> <input type="text" id="lastName" name="last_name" placeholder="Developer"> <input type="password" id="password" name="password" placeholder="Create a password" minlength="6"> <button type="submit" class="btn btn-primary btn-lg form-submit">
Recorded tool call
{
"type": "tool_call",
"seq": 76,
"ts": "2026-10-18T02:40:43.792Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sSL \"https://api.doorprofit.com/signup/\" -o dp_signup.html -w \"HTTP:%{http_code} size:%{size_download}\\n\"; grep -oE '<(input|form|button)[^>]*>' dp_signup.html | head -30"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b3c6933e28268733",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 77,
"ts": "2026-10-18T02:40:44.967Z",
"turn": 1,
"output": {
"content": [
{
"text": "HTTP:200 size:33049\n<button class=\"mobile-menu-toggle\" aria-label=\"Open menu\">\n<button class=\"mobile-menu-close\" aria-label=\"Close menu\">\n<form id=\"apiSignupForm\">\n<input type=\"email\" id=\"email\" name=\"email\" required placeholder=\"Enter your email\">\n<button type=\"button\" id=\"continueBtn\" class=\"btn btn-primary btn-lg form-submit\">\n<button type=\"button\" id=\"changeEmailBtn\" class=\"change-email-btn\">\n<input type=\"text\" id=\"firstName\" name=\"first_name\" placeholder=\"Jane\">\n<input type=\"text\" id=\"lastName\" name=\"last_name\" placeholder=\"Developer\">\n<input type=\"password\" id=\"password\" name=\"password\" placeholder=\"Create a password\" minlength=\"6\">\n<button type=\"submit\" class=\"btn btn-primary btn-lg form-submit\">\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b3c6933e28268733",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Sign-up is possible with just email address + name + password. No CAPTCHA to be found? Let me check for hCaptcha/reCAPTCHA and find the JS submission endpoint. But even if I could sign up, an actual email address is needed for verification/linking purposes. I don't have a mailbox. This is a genuine blocker for obtaining a working key.
To check whether a key can be obtained immediately, let me quickly check the sign-up JS endpoint. No wait, let's not create an account with a fake email address—that would be creating a garbage account. Hmm. But the task is "to create a simple sample using the DoorProfit API." The reasonable deliverable is: a working sample script + a note that a key is required (free signup, instant).
Actually, let me check whether sign-up requires email verification. Let me look at the JS.
Also, I still need pricing info. There's no public pricing page! The docs mention a "pricing page" but it 404s. It might be inside the dashboard after login. Let me check the main site's homepage further and search for pricing info via login/dashboard. Should I also check DoorProfit on RapidAPI? Let me search.
Let me grep the sign-up HTML for the fetch endpoint and CAPTCHA.
cd /tmp && grep -oiE 'captcha|recaptcha|hcaptcha|turnstile' dp_signup.html | sort -u; echo "--- endpoints ---"; grep -oE "(fetch|axios|url:|action=)[^,;)]{0,120}" dp_signup.html | grep -iE "api|signup|register|/v1|auth" | head -20; echo "--- inline js signup ---"; python3 - <<'EOF'
import re
s=open('/tmp/dp_signup.html',encoding='utf-8',errors='replace').read()
for m in re.findall(r'<script(?![^>]*src)[^>]*>(.*?)</script>',s,flags=re.S):
if 'signup' in m.lower() or 'fetch' in m:
print(m[:3000]); print('-----')
EOF--- endpoints ---
--- endpoints ---
fetch('/api/check_email'
fetch('/api/process_signup'
fetch('/api/resend_verification'
--- inline js signup ---
const form = document.getElementById('apiSignupForm');
const step1 = document.getElementById('step1');
const step2 = document.getElementById('step2');
const emailInput = document.getElementById('email');
const emailDisplay = document.getElementById('emailDisplay');
const continueBtn = document.getElementById('continueBtn');
const changeEmailBtn = document.getElementById('changeEmailBtn');
const errorDiv = document.getElementById('errorMessage');
// Step 1: Continue button - validate email, check if exists, then show step 2
continueBtn.addEventListener('click', async function() {
const email = emailInput.value.trim();
// Hide any previous errors
errorDiv.classList.remove('show');
errorDiv.textContent = '';
// Validate email
if (!email) {
errorDiv.textContent = 'Please enter your email address';
errorDiv.classList.add('show');
emailInput.focus();
return;
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
errorDiv.textContent = 'Please enter a valid email address';
errorDiv.classList.add('show');
emailInput.focus();
return;
}
// Check if email already exists
continueBtn.textContent = 'Checking...';
continueBtn.disabled = true;
try {
const formData = new FormData();
formData.append('email', email);
const response = await fetch('/api/check_email', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success && result.exists) {
// Email already exists - show appropriate message
let message = 'An account with this email already exists. ';
if (result.has_google) {
message += '<a href="https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=online&client_id=809086630649-sota4q4mnj76jcamdq640nkld757rf7a.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fapi.doorprofit.com%2Fsignup%2F&state&scope=profile%20email&approval_prompt=auto">Log in with Google</a>';
} else {
message += '<a href="/login/">Log in here</a>';
}
errorDiv.innerHTML = message;
errorDiv.classList.add('show');
continueBtn.textContent = 'Continue';
continueBtn.disabled = false;
-----
Recorded tool call
{
"type": "tool_call",
"seq": 81,
"ts": "2026-10-18T02:40:52.619Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oiE 'captcha|recaptcha|hcaptcha|turnstile' dp_signup.html | sort -u; echo \"--- endpoints ---\"; grep -oE \"(fetch|axios|url:|action=)[^,;)]{0,120}\" dp_signup.html | grep -iE \"api|signup|register|/v1|auth\" | head -20; echo \"--- inline js signup ---\"; python3 - <<'EOF'\nimport re\ns=open('/tmp/dp_signup.html',encoding='utf-8',errors='replace').read()\nfor m in re.findall(r'<script(?![^>]*src)[^>]*>(.*?)</script>',s,flags=re.S):\n if 'signup' in m.lower() or 'fetch' in m:\n print(m[:3000]); print('-----')\nEOF"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81815fc54158bf3f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 83,
"ts": "2026-10-18T02:40:52.957Z",
"turn": 1,
"output": {
"content": [
{
"text": "--- endpoints ---\nfetch('/api/check_email'\nfetch('/api/process_signup'\nfetch('/api/resend_verification'\n--- inline js signup ---\n\n const form = document.getElementById('apiSignupForm');\n const step1 = document.getElementById('step1');\n const step2 = document.getElementById('step2');\n const emailInput = document.getElementById('email');\n const emailDisplay = document.getElementById('emailDisplay');\n const continueBtn = document.getElementById('continueBtn');\n const changeEmailBtn = document.getElementById('changeEmailBtn');\n const errorDiv = document.getElementById('errorMessage');\n \n // Step 1: Continue button - validate email, check if exists, then show step 2\n continueBtn.addEventListener('click', async function() {\n const email = emailInput.value.trim();\n \n // Hide any previous errors\n errorDiv.classList.remove('show');\n errorDiv.textContent = '';\n \n // Validate email\n if (!email) {\n errorDiv.textContent = 'Please enter your email address';\n errorDiv.classList.add('show');\n emailInput.focus();\n return;\n }\n \n // Basic email validation\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(email)) {\n errorDiv.textContent = 'Please enter a valid email address';\n errorDiv.classList.add('show');\n emailInput.focus();\n return;\n }\n \n // Check if email already exists\n continueBtn.textContent = 'Checking...';\n continueBtn.disabled = true;\n \n try {\n const formData = new FormData();\n formData.append('email', email);\n \n const response = await fetch('/api/check_email', {\n method: 'POST',\n body: formData\n });\n \n const result = await response.json();\n \n if (result.success && result.exists) {\n // Email already exists - show appropriate message\n let message = 'An account with this email already exists. ';\n if (result.has_google) {\n message += '<a href=\"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=online&client_id=809086630649-sota4q4mnj76jcamdq640nkld757rf7a.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fapi.doorprofit.com%2Fsignup%2F&state&scope=profile%20email&approval_prompt=auto\">Log in with Google</a>';\n } else {\n message += '<a href=\"/login/\">Log in here</a>';\n }\n errorDiv.innerHTML = message;\n errorDiv.classList.add('show');\n continueBtn.textContent = 'Continue';\n continueBtn.disabled = false;\n \n-----\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-81815fc54158bf3f",
"is_error": false,
"tool_kind": "shell"
}echo "=== rapidapi search ==="; timeout 20 curl -sS "https://rapidapi.com/search/api?searchTerm=doorprofit" 2>&1 | head -c 400; echo; echo "=== google-ish: check /login for dashboard links ==="; cd /tmp && timeout 20 curl -sSL "https://api.doorprofit.com/login/" -o dp_login.html -w "HTTP:%{http_code}\n"; grep -oiE 'pricing|plan|\$[0-9]+' dp_login.html | sort -u | head -20=== rapidapi search ===
=== rapidapi search === <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/hub/_next/static/css/bcef81b5af7295c2.css" data-precedence="next"/><link rel="stylesheet" href="/hub/_next/static/css/f4eaed85f393ab80.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/hub/_next/static/c === google-ish: check /login for dashboard links === HTTP:200
Recorded tool call
{
"type": "tool_call",
"seq": 82,
"ts": "2026-10-18T02:40:52.619Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== rapidapi search ===\"; timeout 20 curl -sS \"https://rapidapi.com/search/api?searchTerm=doorprofit\" 2>&1 | head -c 400; echo; echo \"=== google-ish: check /login for dashboard links ===\"; cd /tmp && timeout 20 curl -sSL \"https://api.doorprofit.com/login/\" -o dp_login.html -w \"HTTP:%{http_code}\\n\"; grep -oiE 'pricing|plan|\\$[0-9]+' dp_login.html | sort -u | head -20"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b8f23e04e2b0e7f0",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 84,
"ts": "2026-10-18T02:40:54.044Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== rapidapi search ===\n<!DOCTYPE html><html lang=\"en\"><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><link rel=\"stylesheet\" href=\"/hub/_next/static/css/bcef81b5af7295c2.css\" data-precedence=\"next\"/><link rel=\"stylesheet\" href=\"/hub/_next/static/css/f4eaed85f393ab80.css\" data-precedence=\"next\"/><link rel=\"preload\" as=\"script\" fetchPriority=\"low\" href=\"/hub/_next/static/c\n=== google-ish: check /login for dashboard links ===\nHTTP:200\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b8f23e04e2b0e7f0",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Since there's a /api/resend_verification endpoint, signup requires email verification. Without a mailbox I can't receive verification emails → can't obtain an API key myself. This is a real (partial) blocker for running the example, but not a blocker for building it.
Pricing: there's no public pricing page (the docs link is broken / 404). However, from /v1/usage and the rate-limit table in the docs, I know the plan tiers: Free (10/month), Starter (300/month), Growth (600/month, $50/month per the usage example), Pro (1,200/month), Enterprise (custom). Overage is $0.10 per call (per the usage example). There's also an embed-view quota: 5,000 free views/month across all plans.
Let me double-check the npm package 'doorprofit' one more time (the hero code on the homepage suggests an SDK).
After that, build the example. Make it a small Python (and/or Node) CLI that wraps the three endpoints + usage, reads DOORPROFIT_API_KEY from env vars, and has caching + graceful error handling. Keep it light — no local server, no long-running commands.
Let me verify that the npm package really doesn't exist, and check whether there's an official SDK on GitHub (the IlijaAngelov/Doorprofit repo looked unrelated).
Let me do these checks and then write the code.
echo "=== npm registry direct ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/doorprofit; echo "=== pypi direct ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://pypi.org/project/doorprofit/; echo "=== github repo check ==="; timeout 20 curl -sS "https://api.github.com/search/repositories?q=doorprofit" | python3 -c "import sys,json;d=json.load(sys.stdin);[print(i['full_name'],'|',i.get('description'),'|stars',i['stargazers_count'],'|',(i.get('language') or '-')) for i in d['items']]"=== npm registry direct ===
=== npm registry direct === 404 === pypi direct === 200 === github repo check === IlijaAngelov/Doorprofit | None |stars 0 | - shawndoor/doorprofit-mcp | None |stars 0 | JavaScript shawndoor/doorprofit-claude-skills | roduces a structured due-diligence report for any US property address using the DoorProfit connector: crime safety score, recent incidents, demographics, income, home values, rent, disaster risk, and registered-offender search. |stars 0 | -
Recorded tool call
{
"type": "tool_call",
"seq": 87,
"ts": "2026-10-18T02:41:01.325Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"=== npm registry direct ===\"; timeout 20 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://registry.npmjs.org/doorprofit; echo \"=== pypi direct ===\"; timeout 20 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://pypi.org/project/doorprofit/; echo \"=== github repo check ===\"; timeout 20 curl -sS \"https://api.github.com/search/repositories?q=doorprofit\" | python3 -c \"import sys,json;d=json.load(sys.stdin);[print(i['full_name'],'|',i.get('description'),'|stars',i['stargazers_count'],'|',(i.get('language') or '-')) for i in d['items']]\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9599c9a9625d8dd9",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 90,
"ts": "2026-10-18T02:41:03.165Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== npm registry direct ===\n404\n=== pypi direct ===\n200\n=== github repo check ===\nIlijaAngelov/Doorprofit | None |stars 0 | -\nshawndoor/doorprofit-mcp | None |stars 0 | JavaScript\nshawndoor/doorprofit-claude-skills | roduces a structured due-diligence report for any US property address using the DoorProfit connector: crime safety score, recent incidents, demographics, income, home values, rent, disaster risk, and registered-offender search. |stars 0 | -\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9599c9a9625d8dd9",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && grep -oE 'href="[^"]*"' dp_docs.html | sed 's/href="//;s/"$//' | sort -u | grep -viE 'fonts|\.png|\.webp|\.ico|manifest|#' | head -30; echo "=== docs nav anchors for pricing ==="; grep -oiE '[^>]{0,60}pricing[^<]{0,60}' dp_docs.html | head -10/
/
/billing
/billing/
/dashboard
/docs/
/login/
/signup/
https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css
https://www.doorprofit.com/
https://www.doorprofit.com//crime-map/
mailto:support@doorprofit.com
=== docs nav anchors for pricing ===
<!-- Embed: Views & Pricing -->
pricing page
pricing page
for plan details and pricing.
Recorded tool call
{
"type": "tool_call",
"seq": 88,
"ts": "2026-10-18T02:41:01.325Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oE 'href=\"[^\"]*\"' dp_docs.html | sed 's/href=\"//;s/\"$//' | sort -u | grep -viE 'fonts|\\.png|\\.webp|\\.ico|manifest|#' | head -30; echo \"=== docs nav anchors for pricing ===\"; grep -oiE '[^>]{0,60}pricing[^<]{0,60}' dp_docs.html | head -10"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-934f0c42ccad283c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 89,
"ts": "2026-10-18T02:41:01.325Z",
"turn": 1,
"output": {
"content": [
{
"text": "/\n/billing\n/billing/\n/dashboard\n/docs/\n/login/\n/signup/\nhttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\nhttps://www.doorprofit.com/\nhttps://www.doorprofit.com//crime-map/\nmailto:support@doorprofit.com\n=== docs nav anchors for pricing ===\n <!-- Embed: Views & Pricing -->\npricing page\npricing page\n for plan details and pricing.\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-934f0c42ccad283c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Interesting! pypi.org/project/doorprofit/ returned 200! But pip index versions doorprofit said there's no matching distribution. Let me check — maybe it's a page that exists but has no files, or maybe pip is using a different index. Let me fetch it.
Also, the docs link to /billing (probably behind a login).
cd /tmp && timeout 25 curl -sSL "https://pypi.org/project/doorprofit/" -o dp_pypi.html -w "HTTP:%{http_code}\n"; python3 - <<'EOF'
import re,html
s=open('/tmp/dp_pypi.html',encoding='utf-8',errors='replace').read()
s2=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)
s2=re.sub(r'<style.*?</style>','',s2,flags=re.S|re.I)
s2=re.sub(r'<[^>]+>',' ',s2); s2=html.unescape(s2); s2=re.sub(r'[ \t]+',' ',s2); s2=re.sub(r'\n\s*\n+','\n',s2)
print(s2.strip()[:4000])
EOFHTTP:200
HTTP:200 Client Challenge JavaScript is disabled in your browser. Please enable JavaScript to proceed. A required part of this site couldn’t load. This may be due to a browser extension, network issues, or browser settings. Please check your connection, disable any ad blockers, or try using a different browser.
Recorded tool call
{
"type": "tool_call",
"seq": 93,
"ts": "2026-10-18T02:41:08.675Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sSL \"https://pypi.org/project/doorprofit/\" -o dp_pypi.html -w \"HTTP:%{http_code}\\n\"; python3 - <<'EOF'\nimport re,html\ns=open('/tmp/dp_pypi.html',encoding='utf-8',errors='replace').read()\ns2=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)\ns2=re.sub(r'<style.*?</style>','',s2,flags=re.S|re.I)\ns2=re.sub(r'<[^>]+>',' ',s2); s2=html.unescape(s2); s2=re.sub(r'[ \\t]+',' ',s2); s2=re.sub(r'\\n\\s*\\n+','\\n',s2)\nprint(s2.strip()[:4000])\nEOF"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-97cbbb2367fe4cac",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 95,
"ts": "2026-10-18T02:41:08.989Z",
"turn": 1,
"output": {
"content": [
{
"text": "HTTP:200\nClient Challenge \n JavaScript is disabled in your browser. \n Please enable JavaScript to proceed. \n A required part of this site couldn’t load. This may be due to a browser\n extension, network issues, or browser settings. Please check your\n connection, disable any ad blockers, or try using a different browser.\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-97cbbb2367fe4cac",
"is_error": false,
"tool_kind": "shell"
}timeout 20 curl -sS "https://pypi.org/pypi/doorprofit/json" | head -c 1500; echo; echo "=== pip retry verbose ==="; timeout 40 pip download doorprofit --no-deps -d /tmp/dp2 2>&1 | tail -8
{"message": "Not Found"}
{"message": "Not Found"}
=== pip retry verbose ===
ERROR: Could not find a version that satisfies the requirement doorprofit (from versions: none)
ERROR: No matching distribution found for doorprofit
Recorded tool call
{
"type": "tool_call",
"seq": 94,
"ts": "2026-10-18T02:41:08.675Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 20 curl -sS \"https://pypi.org/pypi/doorprofit/json\" | head -c 1500; echo; echo \"=== pip retry verbose ===\"; timeout 40 pip download doorprofit --no-deps -d /tmp/dp2 2>&1 | tail -8"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fe0ebb283cca7ce",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 96,
"ts": "2026-10-18T02:41:11.014Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"message\": \"Not Found\"}\n=== pip retry verbose ===\nERROR: Could not find a version that satisfies the requirement doorprofit (from versions: none)\nERROR: No matching distribution found for doorprofit\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fe0ebb283cca7ce",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
That 200 was a JS challenge page, not an actual project. Confirmed: there is no official doorprofit SDK on PyPI or npm. The import DoorProfit from 'doorprofit' snippet on the homepage is aspirational/pseudocode. Therefore, REST (or the hosted MCP server) is the only means.
Let me quickly check the two GitHub repos — shawndoor/doorprofit-mcp and doorprofit-claude-skills. "shawndoor" might be the owner of DoorProfit! Let's check.
After that, I'll build an example.
for r in shawndoor/doorprofit-mcp shawndoor/doorprofit-claude-skills; do echo "=== $r ==="; timeout 20 curl -sS "https://api.github.com/repos/$r" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('full_name'),'|',d.get('description'),'| pushed:',d.get('pushed_at'),'| default:',d.get('default_branch'))"; timeout 20 curl -sS "https://api.github.com/repos/$r/git/trees/HEAD?recursive=1" | python3 -c "import sys,json;d=json.load(sys.stdin);[print(' ',t['path']) for t in d.get('tree',[])[:25]]" 2>/dev/null; done; echo "=== user shawndoor ==="; timeout 20 curl -sS "https://api.github.com/users/shawndoor" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('name'),d.get('blog'),d.get('bio'))"=== shawndoor/doorprofit-mcp ===
=== shawndoor/doorprofit-mcp === shawndoor/doorprofit-mcp | None | pushed: 2026-08-03T18:10:00Z | default: main .github .github/workflows .github/workflows/ci.yml .gitignore Dockerfile LICENSE README.md glama.json package-lock.json package.json scripts scripts/smoke-test.js scripts/sync-tools.js server.json src src/index.js src/remote.js src/tools.json === shawndoor/doorprofit-claude-skills === shawndoor/doorprofit-claude-skills | roduces a structured due-diligence report for any US property address using the DoorProfit connector: crime safety score, recent incidents, demographics, income, home values, rent, disaster risk, and registered-offender search. | pushed: 2026-06-11T03:11:19Z | default: main LICENSE README.md property-due-diligence property-due-diligence/SKILL.md === user shawndoor === None None
Recorded tool call
{
"type": "tool_call",
"seq": 99,
"ts": "2026-10-18T02:41:18.679Z",
"name": "bash",
"turn": 1,
"input": {
"command": "for r in shawndoor/doorprofit-mcp shawndoor/doorprofit-claude-skills; do echo \"=== $r ===\"; timeout 20 curl -sS \"https://api.github.com/repos/$r\" | python3 -c \"import sys,json;d=json.load(sys.stdin);print(d.get('full_name'),'|',d.get('description'),'| pushed:',d.get('pushed_at'),'| default:',d.get('default_branch'))\"; timeout 20 curl -sS \"https://api.github.com/repos/$r/git/trees/HEAD?recursive=1\" | python3 -c \"import sys,json;d=json.load(sys.stdin);[print(' ',t['path']) for t in d.get('tree',[])[:25]]\" 2>/dev/null; done; echo \"=== user shawndoor ===\"; timeout 20 curl -sS \"https://api.github.com/users/shawndoor\" | python3 -c \"import sys,json;d=json.load(sys.stdin);print(d.get('name'),d.get('blog'),d.get('bio'))\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b9d8353978b97deb",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 102,
"ts": "2026-10-18T02:41:21.850Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== shawndoor/doorprofit-mcp ===\nshawndoor/doorprofit-mcp | None | pushed: 2026-08-03T18:10:00Z | default: main\n .github\n .github/workflows\n .github/workflows/ci.yml\n .gitignore\n Dockerfile\n LICENSE\n README.md\n glama.json\n package-lock.json\n package.json\n scripts\n scripts/smoke-test.js\n scripts/sync-tools.js\n server.json\n src\n src/index.js\n src/remote.js\n src/tools.json\n=== shawndoor/doorprofit-claude-skills ===\nshawndoor/doorprofit-claude-skills | roduces a structured due-diligence report for any US property address using the DoorProfit connector: crime safety score, recent incidents, demographics, income, home values, rent, disaster risk, and registered-offender search. | pushed: 2026-06-11T03:11:19Z | default: main\n LICENSE\n README.md\n property-due-diligence\n property-due-diligence/SKILL.md\n=== user shawndoor ===\nNone None\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b9d8353978b97deb",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 25 curl -sS -X POST "https://api.doorprofit.com/mcp" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0.0.1"}}}' -w "\nHTTP:%{http_code}\n" 2>&1 | head -c 1200{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"doorprofit","title":"DoorProfit Crime & Neigh
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"doorprofit","title":"DoorProfit Crime & Neighborhood Data","version":"1.0.0"},"instructions":"DoorProfit provides US crime, safety, neighborhood, and registered-offender data. Authentication: a DoorProfit API key is required for tool calls, sent as \"Authorization: Bearer <key>\" (get a free key at https://api.doorprofit.com/signup). Quotas: every data tool call consumes 1 API call from the key's monthly/daily plan quota; get_api_usage is free and reports remaining quota. If a call fails with QUOTA_EXCEEDED or DAILY_LIMIT_EXCEEDED, tell the user their DoorProfit quota is exhausted and they can upgrade at https://api.doorprofit.com/billing. The address \"500 Main St, Dallas, TX 75202\" is a free test address that never consumes quota. Docs: https://api.doorprofit.com/docs/"}}
HTTP:200
Recorded tool call
{
"type": "tool_call",
"seq": 100,
"ts": "2026-10-18T02:41:18.679Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sS -X POST \"https://api.doorprofit.com/mcp\" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"probe\",\"version\":\"0.0.1\"}}}' -w \"\\nHTTP:%{http_code}\\n\" 2>&1 | head -c 1200"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fdbb28188caabc9",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 101,
"ts": "2026-10-18T02:41:19.452Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"doorprofit\",\"title\":\"DoorProfit Crime & Neighborhood Data\",\"version\":\"1.0.0\"},\"instructions\":\"DoorProfit provides US crime, safety, neighborhood, and registered-offender data. Authentication: a DoorProfit API key is required for tool calls, sent as \\\"Authorization: Bearer <key>\\\" (get a free key at https://api.doorprofit.com/signup). Quotas: every data tool call consumes 1 API call from the key's monthly/daily plan quota; get_api_usage is free and reports remaining quota. If a call fails with QUOTA_EXCEEDED or DAILY_LIMIT_EXCEEDED, tell the user their DoorProfit quota is exhausted and they can upgrade at https://api.doorprofit.com/billing. The address \\\"500 Main St, Dallas, TX 75202\\\" is a free test address that never consumes quota. Docs: https://api.doorprofit.com/docs/\"}}\nHTTP:200\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8fdbb28188caabc9",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Excellent! The hosted MCP server works and can respond even without authentication (initialize). Tool calls require a key. This is a developer workflow that I can actually handle — I can communicate with it directly.
Let me try listing tools via MCP without a key (tools/list might work even without authentication).
Next, build an example. What to create:
- A small Python client (
doorprofit_example/) that wraps REST: crime, neighborhood, offenders, usage. Read the key from an environment variable, add caching, do graceful error handling, and a CLI that generates a property due diligence report. - MCP client configuration + a script that communicates with the hosted MCP server.
- A README documenting pricing and setup.
First, let me check tools/list.
cd /tmp && timeout 25 curl -sS -X POST "https://api.doorprofit.com/mcp" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' 2>&1 | python3 -c "
import sys,json
raw=sys.stdin.read()
raw=raw.split('data: ')[-1] if raw.startswith('event') else raw
try:
d=json.loads(raw)
for t in d['result']['tools']:
print('*',t['name'],'::',(t.get('description') or '')[:90])
print(' params:',list((t.get('inputSchema') or {}).get('properties',{}).keys()), 'required:',(t.get('inputSchema') or {}).get('required'))
except Exception as e:
print('ERR',e); print(raw[:800])
"* get_crime_data :: Crime safety grade and nearby incident counts for one US address or coordinate pair. Retur
* get_crime_data :: Crime safety grade and nearby incident counts for one US address or coordinate pair. Retur params: ['address', 'lat', 'lng'] required: None * get_neighborhood_data :: Profile of the neighborhood (or census block group) containing one US address or coordinat params: ['address', 'lat', 'lng'] required: None * search_offenders :: Search US registered offenders in one of two modes. Location mode: pass address or lat+lng params: ['address', 'lat', 'lng', 'radius', 'first_name', 'last_name', 'dob', 'city', 'state', 'zipcode', 'page', 'limit'] required: None * get_api_usage :: Report the plan, monthly and daily call usage, remaining quota, reset times, rate limit an params: [] required: None
Recorded tool call
{
"type": "tool_call",
"seq": 105,
"ts": "2026-10-18T02:41:29.008Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sS -X POST \"https://api.doorprofit.com/mcp\" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}' 2>&1 | python3 -c \"\nimport sys,json\nraw=sys.stdin.read()\nraw=raw.split('data: ')[-1] if raw.startswith('event') else raw\ntry:\n d=json.loads(raw)\n for t in d['result']['tools']:\n print('*',t['name'],'::',(t.get('description') or '')[:90])\n print(' params:',list((t.get('inputSchema') or {}).get('properties',{}).keys()), 'required:',(t.get('inputSchema') or {}).get('required'))\nexcept Exception as e:\n print('ERR',e); print(raw[:800])\n\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8445a619360dbff3",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 108,
"ts": "2026-10-18T02:41:29.325Z",
"turn": 1,
"output": {
"content": [
{
"text": "* get_crime_data :: Crime safety grade and nearby incident counts for one US address or coordinate pair. Retur\n params: ['address', 'lat', 'lng'] required: None\n* get_neighborhood_data :: Profile of the neighborhood (or census block group) containing one US address or coordinat\n params: ['address', 'lat', 'lng'] required: None\n* search_offenders :: Search US registered offenders in one of two modes. Location mode: pass address or lat+lng\n params: ['address', 'lat', 'lng', 'radius', 'first_name', 'last_name', 'dob', 'city', 'state', 'zipcode', 'page', 'limit'] required: None\n* get_api_usage :: Report the plan, monthly and daily call usage, remaining quota, reset times, rate limit an\n params: [] required: None\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-8445a619360dbff3",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && timeout 25 curl -sS -X POST "https://api.doorprofit.com/mcp" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_crime_data","arguments":{"address":"500 Main St, Dallas, TX 75202"}}}' -w "\nHTTP:%{http_code}\n" 2>&1 | head -c 900{"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"Unauthorized: authenticate via OAuth, or supply a DoorProfit API key as \"Authorization: Bearer <ke
{"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"Unauthorized: authenticate via OAuth, or supply a DoorProfit API key as \"Authorization: Bearer <key>\". Get a free key at https://api.doorprofit.com/signup"}}
HTTP:401
Recorded tool call
{
"type": "tool_call",
"seq": 106,
"ts": "2026-10-18T02:41:29.008Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sS -X POST \"https://api.doorprofit.com/mcp\" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"get_crime_data\",\"arguments\":{\"address\":\"500 Main St, Dallas, TX 75202\"}}}' -w \"\\nHTTP:%{http_code}\\n\" 2>&1 | head -c 900"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b553a6596f406a2b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 107,
"ts": "2026-10-18T02:41:29.325Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32001,\"message\":\"Unauthorized: authenticate via OAuth, or supply a DoorProfit API key as \\\"Authorization: Bearer <key>\\\". Get a free key at https://api.doorprofit.com/signup\"}}\nHTTP:401\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b553a6596f406a2b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Confirmed: everything is gated behind an API key that requires account registration + email verification. Without a mailbox, I can't obtain a key.
So: the product actually has a developer workflow that I can engage with (hosted REST + hosted MCP). The only obstacle to live execution is the API key (email verification required). What needs to be done:
- Clearly state the pricing structure.
- Build an example (REST client + MCP configuration + CLI report) — a dry-run/offline mode so that the structure can be verified without a key, with the ability to run live when DOORPROFIT_API_KEY is provided.
- Tell them whether it's easy for me to manage.
- Clearly state the obstacle: can't obtain an API key myself (email verification required), so live calls are blocked until they paste in a key.
Let's build it. Keep it lightweight: pure standard library Python (no dependencies) + Node variant + MCP configuration. Keep it to a minimal but actually usable form.
Let me check the repo's .gitignore and Python version.
cd /sandbox/repo && cat .gitignore; echo "--- python ---"; python3 -V; echo "--- node ---"; node -v
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs. # Logs / OS cruft *.log .DS_Store # JavaScript / TypeScript node_modules/ .next/ dist/ build/ coverage/ # Python .venv/ venv/ __pycache__/ *.py[cod] .pytest_cache/ *.egg-info/ # Rust target/ # Go / PHP / Ruby vendored deps vendor/ .bundle/ # Java / Kotlin / Scala .gradle/ # .NET bin/ obj/ # Elixir _build/ deps/ # Dart / Flutter .dart_tool/ --- python --- Python 3.12.3 --- node --- v22.23.2
Recorded tool call
{
"type": "tool_call",
"seq": 111,
"ts": "2026-10-18T02:41:35.261Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat .gitignore; echo \"--- python ---\"; python3 -V; echo \"--- node ---\"; node -v"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9d15958bbbece6bf",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 112,
"ts": "2026-10-18T02:41:35.261Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n--- python ---\nPython 3.12.3\n--- node ---\nv22.23.2\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9d15958bbbece6bf",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Alright, let's create a sample. Structure:
/sandbox/repo/doorprofit-example/
README.md
doorprofit.py # Small REST client using only the standard library (Python 3.9+)
report.py # CLI: property due diligence report (crime + district + offender + usage)
report.mjs # Node version (fetch-based, no dependencies)
mcp-config.json # MCP config for hosted MCP server
crime-map-widget.html # Example embeddable widget
.env.example
Keep it concise. Let's write doorprofit.py properly: errors, retry on 429 (with backoff), respect rate limits, disk cache to save quota (docs recommend caching), headers for quota tracking.
Let's write the files.
Everything checks out. Now let me build the example.
File edits
1 file
Writtendoorprofit-example/doorprofit.pyView changes
/sandbox/repo/doorprofit-example/doorprofit.py
Written content · previous contents were not recorded
"""Minimal DoorProfit API client.DoorProfit is a hosted REST API (no local stack, no official SDK on PyPI/npm),so this is a thin stdlib-only wrapper over:GET https://api.doorprofit.com/v1/crimeGET https://api.doorprofit.com/v1/neighborhoodGET https://api.doorprofit.com/v1/offendersGET https://api.doorprofit.com/v1/usage (free, never counts against quota)Docs: https://api.doorprofit.com/docs/Design notes (driven by how the product meters you):* Every data call costs 1 credit from a small monthly quota (10/month free),so responses are cached on disk by default. Re-running is free.* The address "500 Main St, Dallas, TX 75202" is a documented free testaddress that never consumes quota -- used as the default demo target.* 429s are retried with backoff; QUOTA_EXCEEDED is raised, not retried.Requires Python 3.9+. No third-party dependencies."""from __future__ import annotationsimport jsonimport osimport timeimport urllib.errorimport urllib.parseimport urllib.requestfrom pathlib import Pathfrom typing import Any, Dict, OptionalBASE_URL = "https://api.doorprofit.com/v1"#: Documented free test address -- calls against it do not consume quota.FREE_TEST_ADDRESS = "500 Main St, Dallas, TX 75202"USER_AGENT = "doorprofit-example/1.0 (+stdlib urllib)"class DoorProfitError(Exception):"""Base error. Carries the structured error body the API returns."""def __init__(self, message: str, *, code: Optional[str] = None,status: Optional[int] = None, doc_url: Optional[str] = None):super().__init__(message)self.code = codeself.status = statusself.doc_url = doc_url@propertydef retryable(self) -> bool:return self.code == "RATE_LIMIT_EXCEEDED" or self.status == 429@propertydef out_of_quota(self) -> bool:return self.code in {"QUOTA_EXCEEDED", "DAILY_LIMIT_EXCEEDED"}class DoorProfit:"""Tiny client for the three DoorProfit data endpoints plus /usage."""def __init__(self,api_key: Optional[str] = None,*,cache_dir: Optional[os.PathLike | str] = ".doorprofit-cache",max_retries: int = 3,timeout: float = 20.0,) -> None:self.api_key = api_key or os.environ.get("DOORPROFIT_API_KEY", "")if not self.api_key:raise DoorProfitError("No API key. Set DOORPROFIT_API_KEY or pass api_key=... ""(free key: https://api.doorprofit.com/signup/)")self.cache_dir = Path(cache_dir) if cache_dir else Noneself.max_retries = max_retriesself.timeout = timeoutself.last_usage_headers: Dict[str, str] = {}# ------------------------------------------------------------------ HTTPdef _request(self, path: str, params: Dict[str, Any]) -> Dict[str, Any]:query = {k: v for k, v in params.items() if v is not None}query["key"] = self.api_keyurl = f"{BASE_URL}/{path.lstrip('/')}?{urllib.parse.urlencode(query)}"attempt = 0while True:attempt += 1req = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": USER_AGENT})try:with urllib.request.urlopen(req, timeout=self.timeout) as resp:body = resp.read().decode("utf-8")self.last_usage_headers = {k.lower(): v for k, v in resp.headers.items()if k.lower().startswith(("x-",))}return json.loads(body)except urllib.error.HTTPError as exc:raw = exc.read().decode("utf-8", "replace")err = _parse_error(raw, exc.code)if err.retryable and attempt <= self.max_retries:time.sleep(min(2 ** attempt, 8)) # back off, then retrycontinueraise err from Noneexcept urllib.error.URLError as exc:if attempt <= self.max_retries:time.sleep(min(2 ** attempt, 8))continueraise DoorProfitError(f"Network error: {exc.reason}") from None# ----------------------------------------------------------------- cachedef _cache_key(self, path: str, params: Dict[str, Any]) -> Optional[Path]:if self.cache_dir is None:return Noneclean = {k: v for k, v in sorted(params.items()) if v is not None}digest = urllib.parse.urlencode(clean)self.cache_dir.mkdir(parents=True, exist_ok=True)return self.cache_dir / f"{path.strip('/')}--{urllib.parse.quote(digest, safe='')}.json"def _get(self, path: str, params: Dict[str, Any], *, use_cache: bool = True) -> Dict[str, Any]:slot = self._cache_key(path, params)if use_cache and slot and slot.exists():return json.loads(slot.read_text())data = self._request(path, params)if slot and data.get("success"):slot.write_text(json.dumps(data, indent=2))return data# ------------------------------------------------------------- endpoints@staticmethoddef _location(address: Optional[str], lat: Optional[float],lng: Optional[float]) -> Dict[str, Any]:if address:return {"address": address}if lat is not None and lng is not None:return {"lat": lat, "lng": lng}raise DoorProfitError("Provide either address= or both lat= and lng=")def crime(self, address: Optional[str] = None, *, lat: Optional[float] = None,lng: Optional[float] = None, use_cache: bool = True) -> Dict[str, Any]:"""Safety grade (A+..F), numeric score, and nearby incident counts."""return self._get("/crime", self._location(address, lat, lng), use_cache=use_cache)def neighborhood(self, address: Optional[str] = None, *, lat: Optional[float] = None,lng: Optional[float] = None, use_cache: bool = True) -> Dict[str, Any]:"""Demographics, income, housing, education, cost of living, rent, weather."""return self._get("/neighborhood", self._location(address, lat, lng), use_cache=use_cache)def offenders(self, address: Optional[str] = None, *, lat: Optional[float] = None,lng: Optional[float] = None, radius: float = 1.0,first_name: Optional[str] = None, last_name: Optional[str] = None,city: Optional[str] = None, state: Optional[str] = None,zipcode: Optional[str] = None, page: int = 1, limit: int = 50,use_cache: bool = True) -> Dict[str, Any]:"""Registered offenders near a location, or by name. Each page = 1 credit."""params: Dict[str, Any] = {"radius": radius, "page": page, "limit": limit}if address or (lat is not None and lng is not None):params.update(self._location(address, lat, lng))for key, val in (("first_name", first_name), ("last_name", last_name),("city", city), ("state", state), ("zipcode", zipcode)):if val:params[key] = valreturn self._get("/offenders", params, use_cache=use_cache)def usage(self) -> Dict[str, Any]:"""Plan, quota, and remaining calls. FREE -- never cached, never metered."""return self._request("/usage", {})# ------------------------------------------------------------ nicetiesdef quota_summary(self) -> str:"""One-line human-readable quota readout from /v1/usage."""data = self.usage().get("data", {})plan = data.get("plan", {})monthly = data.get("monthly", {})daily = data.get("daily", {})overage = data.get("overage", {})bits = [f"plan={plan.get('name', '?')}",f"${plan.get('price_monthly', 0)}/mo",f"month={monthly.get('used', '?')}/{monthly.get('limit', '?')}",f"today={daily.get('used', '?')}/{daily.get('limit', '?')}",f"rate={data.get('rate_limit', {}).get('requests_per_second', '?')} req/s",]if overage.get("enabled"):bits.append(f"overage=${overage.get('rate_per_call', '?')}/call")return " ".join(bits)def _parse_error(raw: str, status: int) -> DoorProfitError:try:body = json.loads(raw)err = body.get("error", {}) or {}return DoorProfitError(err.get("message") or raw[:200] or f"HTTP {status}",code=err.get("code"),status=status,doc_url=err.get("documentation_url"),)except (json.JSONDecodeError, AttributeError):return DoorProfitError(raw[:200] or f"HTTP {status}", status=status)
Source: write · chatcmpl-tool-b0cd6c6acd3ace6e
Recorded tool call
{
"type": "tool_call",
"seq": 116,
"ts": "2026-10-18T02:42:03.850Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/doorprofit.py",
"content": "\"\"\"Minimal DoorProfit API client.\n\nDoorProfit is a hosted REST API (no local stack, no official SDK on PyPI/npm),\nso this is a thin stdlib-only wrapper over:\n\n GET https://api.doorprofit.com/v1/crime\n GET https://api.doorprofit.com/v1/neighborhood\n GET https://api.doorprofit.com/v1/offenders\n GET https://api.doorprofit.com/v1/usage (free, never counts against quota)\n\nDocs: https://api.doorprofit.com/docs/\n\nDesign notes (driven by how the product meters you):\n * Every data call costs 1 credit from a small monthly quota (10/month free),\n so responses are cached on disk by default. Re-running is free.\n * The address \"500 Main St, Dallas, TX 75202\" is a documented free test\n address that never consumes quota -- used as the default demo target.\n * 429s are retried with backoff; QUOTA_EXCEEDED is raised, not retried.\n\nRequires Python 3.9+. No third-party dependencies.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Any, Dict, Optional\n\nBASE_URL = \"https://api.doorprofit.com/v1\"\n\n#: Documented free test address -- calls against it do not consume quota.\nFREE_TEST_ADDRESS = \"500 Main St, Dallas, TX 75202\"\n\nUSER_AGENT = \"doorprofit-example/1.0 (+stdlib urllib)\"\n\n\nclass DoorProfitError(Exception):\n \"\"\"Base error. Carries the structured error body the API returns.\"\"\"\n\n def __init__(self, message: str, *, code: Optional[str] = None,\n status: Optional[int] = None, doc_url: Optional[str] = None):\n super().__init__(message)\n self.code = code\n self.status = status\n self.doc_url = doc_url\n\n @property\n def retryable(self) -> bool:\n return self.code == \"RATE_LIMIT_EXCEEDED\" or self.status == 429\n\n @property\n def out_of_quota(self) -> bool:\n return self.code in {\"QUOTA_EXCEEDED\", \"DAILY_LIMIT_EXCEEDED\"}\n\n\nclass DoorProfit:\n \"\"\"Tiny client for the three DoorProfit data endpoints plus /usage.\"\"\"\n\n def __init__(\n self,\n api_key: Optional[str] = None,\n *,\n cache_dir: Optional[os.PathLike | str] = \".doorprofit-cache\",\n max_retries: int = 3,\n timeout: float = 20.0,\n ) -> None:\n self.api_key = api_key or os.environ.get(\"DOORPROFIT_API_KEY\", \"\")\n if not self.api_key:\n raise DoorProfitError(\n \"No API key. Set DOORPROFIT_API_KEY or pass api_key=... \"\n \"(free key: https://api.doorprofit.com/signup/)\"\n )\n self.cache_dir = Path(cache_dir) if cache_dir else None\n self.max_retries = max_retries\n self.timeout = timeout\n self.last_usage_headers: Dict[str, str] = {}\n\n # ------------------------------------------------------------------ HTTP\n\n def _request(self, path: str, params: Dict[str, Any]) -> Dict[str, Any]:\n query = {k: v for k, v in params.items() if v is not None}\n query[\"key\"] = self.api_key\n url = f\"{BASE_URL}/{path.lstrip('/')}?{urllib.parse.urlencode(query)}\"\n\n attempt = 0\n while True:\n attempt += 1\n req = urllib.request.Request(\n url, headers={\"Accept\": \"application/json\", \"User-Agent\": USER_AGENT}\n )\n try:\n with urllib.request.urlopen(req, timeout=self.timeout) as resp:\n body = resp.read().decode(\"utf-8\")\n self.last_usage_headers = {\n k.lower(): v for k, v in resp.headers.items()\n if k.lower().startswith((\"x-\",))\n }\n return json.loads(body)\n except urllib.error.HTTPError as exc:\n raw = exc.read().decode(\"utf-8\", \"replace\")\n err = _parse_error(raw, exc.code)\n if err.retryable and attempt <= self.max_retries:\n time.sleep(min(2 ** attempt, 8)) # back off, then retry\n continue\n raise err from None\n except urllib.error.URLError as exc:\n if attempt <= self.max_retries:\n time.sleep(min(2 ** attempt, 8))\n continue\n raise DoorProfitError(f\"Network error: {exc.reason}\") from None\n\n # ----------------------------------------------------------------- cache\n\n def _cache_key(self, path: str, params: Dict[str, Any]) -> Optional[Path]:\n if self.cache_dir is None:\n return None\n clean = {k: v for k, v in sorted(params.items()) if v is not None}\n digest = urllib.parse.urlencode(clean)\n self.cache_dir.mkdir(parents=True, exist_ok=True)\n return self.cache_dir / f\"{path.strip('/')}--{urllib.parse.quote(digest, safe='')}.json\"\n\n def _get(self, path: str, params: Dict[str, Any], *, use_cache: bool = True) -> Dict[str, Any]:\n slot = self._cache_key(path, params)\n if use_cache and slot and slot.exists():\n return json.loads(slot.read_text())\n data = self._request(path, params)\n if slot and data.get(\"success\"):\n slot.write_text(json.dumps(data, indent=2))\n return data\n\n # ------------------------------------------------------------- endpoints\n\n @staticmethod\n def _location(address: Optional[str], lat: Optional[float],\n lng: Optional[float]) -> Dict[str, Any]:\n if address:\n return {\"address\": address}\n if lat is not None and lng is not None:\n return {\"lat\": lat, \"lng\": lng}\n raise DoorProfitError(\"Provide either address= or both lat= and lng=\")\n\n def crime(self, address: Optional[str] = None, *, lat: Optional[float] = None,\n lng: Optional[float] = None, use_cache: bool = True) -> Dict[str, Any]:\n \"\"\"Safety grade (A+..F), numeric score, and nearby incident counts.\"\"\"\n return self._get(\"/crime\", self._location(address, lat, lng), use_cache=use_cache)\n\n def neighborhood(self, address: Optional[str] = None, *, lat: Optional[float] = None,\n lng: Optional[float] = None, use_cache: bool = True) -> Dict[str, Any]:\n \"\"\"Demographics, income, housing, education, cost of living, rent, weather.\"\"\"\n return self._get(\"/neighborhood\", self._location(address, lat, lng), use_cache=use_cache)\n\n def offenders(self, address: Optional[str] = None, *, lat: Optional[float] = None,\n lng: Optional[float] = None, radius: float = 1.0,\n first_name: Optional[str] = None, last_name: Optional[str] = None,\n city: Optional[str] = None, state: Optional[str] = None,\n zipcode: Optional[str] = None, page: int = 1, limit: int = 50,\n use_cache: bool = True) -> Dict[str, Any]:\n \"\"\"Registered offenders near a location, or by name. Each page = 1 credit.\"\"\"\n params: Dict[str, Any] = {\"radius\": radius, \"page\": page, \"limit\": limit}\n if address or (lat is not None and lng is not None):\n params.update(self._location(address, lat, lng))\n for key, val in ((\"first_name\", first_name), (\"last_name\", last_name),\n (\"city\", city), (\"state\", state), (\"zipcode\", zipcode)):\n if val:\n params[key] = val\n return self._get(\"/offenders\", params, use_cache=use_cache)\n\n def usage(self) -> Dict[str, Any]:\n \"\"\"Plan, quota, and remaining calls. FREE -- never cached, never metered.\"\"\"\n return self._request(\"/usage\", {})\n\n # ------------------------------------------------------------ niceties\n\n def quota_summary(self) -> str:\n \"\"\"One-line human-readable quota readout from /v1/usage.\"\"\"\n data = self.usage().get(\"data\", {})\n plan = data.get(\"plan\", {})\n monthly = data.get(\"monthly\", {})\n daily = data.get(\"daily\", {})\n overage = data.get(\"overage\", {})\n bits = [\n f\"plan={plan.get('name', '?')}\",\n f\"${plan.get('price_monthly', 0)}/mo\",\n f\"month={monthly.get('used', '?')}/{monthly.get('limit', '?')}\",\n f\"today={daily.get('used', '?')}/{daily.get('limit', '?')}\",\n f\"rate={data.get('rate_limit', {}).get('requests_per_second', '?')} req/s\",\n ]\n if overage.get(\"enabled\"):\n bits.append(f\"overage=${overage.get('rate_per_call', '?')}/call\")\n return \" \".join(bits)\n\n\ndef _parse_error(raw: str, status: int) -> DoorProfitError:\n try:\n body = json.loads(raw)\n err = body.get(\"error\", {}) or {}\n return DoorProfitError(\n err.get(\"message\") or raw[:200] or f\"HTTP {status}\",\n code=err.get(\"code\"),\n status=status,\n doc_url=err.get(\"documentation_url\"),\n )\n except (json.JSONDecodeError, AttributeError):\n return DoorProfitError(raw[:200] or f\"HTTP {status}\", status=status)\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b0cd6c6acd3ace6e",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 117,
"ts": "2026-10-18T02:42:03.850Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 8954 bytes to /sandbox/repo/doorprofit-example/doorprofit.py",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b0cd6c6acd3ace6e",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
I need to investigate this further. Let me confirm the details.
Next is the report CLI. This calls crime, neighborhood, and offenders — that's 3 credits. The free tier is 10 per month. I'll warn about the cost and add a --usage-only mode and a --dry-run mode that runs without a key (displaying the request contents). Alright, let's create it.
File edits
1 file
Writtendoorprofit-example/report.pyView changes
/sandbox/repo/doorprofit-example/report.py
Written content · previous contents were not recorded
#!/usr/bin/env python3"""Property due-diligence report built on the DoorProfit API.Usage:export DOORPROFIT_API_KEY=your_keypython3 report.py "500 Adams St, Tulsa, OK"python3 report.py --usage-only # free, never touches quotapython3 report.py --dry-run "..." # show requests, spend 0 creditspython3 report.py --json "..." # machine-readable outputCost: one full report = 3 API credits (crime + neighborhood + offenders).The free plan is 10 credits/month, so results are cached on disk and re-runsagainst the same address are free. The documented test address"500 Main St, Dallas, TX 75202" never consumes quota."""from __future__ import annotationsimport argparseimport jsonimport sysimport urllib.parsefrom doorprofit import (BASE_URL, FREE_TEST_ADDRESS, DoorProfit, DoorProfitError)GRADE_SCALE = ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-","D+", "D", "D-", "F"]def bar(value: float, width: int = 20) -> str:"""Render a 0..1 score as a little bar; lower is safer."""filled = max(0, min(width, round(value * width)))return "[" + "#" * filled + "." * (width - filled) + "]"def fmt_money(n) -> str:return f"${n:,.0f}" if isinstance(n, (int, float)) else "n/a"def print_report(crime: dict, hood: dict, offenders: dict, address: str) -> None:loc = crime.get("location") or hood.get("location") or {}where = loc.get("address") or addressprint("=" * 68)print(f" DoorProfit due diligence: {where}")if loc.get("lat") is not None:print(f" {loc.get('lat')}, {loc.get('lng')} | "f"{loc.get('city', '')}, {loc.get('state', '')} "f"{loc.get('zipcode', '')}".rstrip())print("=" * 68)# ---- Crime -----------------------------------------------------------grade = crime.get("crime_score", "?")numeric = crime.get("crime_numeric")print("\nSAFETY")idx = GRADE_SCALE.index(grade) if grade in GRADE_SCALE else Nonerank = f"{idx + 1}/{len(GRADE_SCALE)} (A+ safest)" if idx is not None else ""print(f" Grade {grade} {rank}")if numeric is not None:print(f" Crime score {numeric:.2f} / 1.00 {bar(numeric)} (lower = safer)")print(f" Source {crime.get('crime_score_source', 'n/a')}")print(f" Description {crime.get('crime_description', 'n/a')}")incidents = crime.get("incidents")if incidents:print(f"\n INCIDENTS within {incidents.get('radius_feet', '?')} ft, "f"{incidents.get('date_from', '?')} -> {incidents.get('date_to', '?')}")print(f" Total {incidents.get('count', 0)}")by_type = incidents.get("by_type") or {}top = sorted(by_type.items(), key=lambda kv: kv[1], reverse=True)for name, count in top:if count:print(f" {name.replace('_', ' '):<18}{count:>5}")zeroed = [n for n, c in by_type.items() if not c]if zeroed:print(f" none reported: {', '.join(z.replace('_', ' ') for z in zeroed)}")else:print("\n INCIDENTS not published for this city (incidents: null)")# ---- Neighborhood ----------------------------------------------------nb = hood.get("neighborhood") or {}if nb:demo = nb.get("demographics") or {}income = nb.get("income") or {}housing = nb.get("housing") or {}rent = nb.get("rent") or {}edu = nb.get("education") or {}col = nb.get("cost_of_living") or {}risk = ((nb.get("weather") or {}).get("natural_disaster_risk") or {})print("\nNEIGHBORHOOD")print(f" Name {nb.get('name', 'n/a')} ({nb.get('type', '?')})")print(f" Population {demo.get('population', 'n/a'):,} "f"| density {demo.get('population_density', 'n/a')}/sq mi "f"| median age {demo.get('median_age', 'n/a')}"if isinstance(demo.get("population"), (int, float)) elsef" Population {demo.get('population', 'n/a')}")print(f" Median income {fmt_money(income.get('median'))}")print(f" Median home {fmt_money(housing.get('median_home_value'))}")print(f" Median rent {fmt_money(rent.get('median'))}/mo", end="")by_bed = rent.get("by_bedroom") or {}if by_bed:print(" (" + ", ".join(f"{k} {fmt_money(v)}" for k, v in by_bed.items()) + ")")else:print()print(f" Bachelor's+ {edu.get('bachelors_pct', 'n/a')}% "f"| grad {edu.get('graduate_pct', 'n/a')}%")print(f" Cost of living {col.get('overall_index', 'n/a')} (100 = US avg)")if risk:print(f" Disaster risk overall {risk.get('overall_index', 'n/a')}"f" tornado {risk.get('tornado', 'n/a')}"f" hurricane {risk.get('hurricane', 'n/a')}"f" earthquake {risk.get('earthquake', 'n/a')}")# ---- Offenders -------------------------------------------------------print("\nREGISTERED OFFENDERS")if offenders.get("success") is False:print(f" {(offenders.get('error') or {}).get('message', 'unavailable')}")else:count = offenders.get("offenders_count")radius = offenders.get("radius_miles") or offenders.get("radius") or 1print(f" Within {radius} mi {count if count is not None else 'n/a'} record(s)")for o in (offenders.get("offenders") or [])[:5]:name = o.get("name") or " ".join(filter(None, [o.get("first_name"), o.get("last_name")])) or "?"dist = o.get("distance_miles", o.get("distance"))line = f" - {name}"if o.get("offense"):line += f" | {o['offense']}"if dist is not None:line += f" | {dist} mi"print(line)remaining = (count or 0) - len(offenders.get("offenders") or [])if remaining > 0:print(f" ... and {remaining} more (paginate with page=/limit=)")print("\n" + "=" * 68)print(" Data: DoorProfit (https://www.doorprofit.com). ""Verify before making decisions.")print("=" * 68)def dry_run(address: str, radius: float) -> None:"""Print exactly what would be sent, and what it would cost. Spends nothing."""key = "YOUR_API_KEY"calls = [("crime", {"address": address}),("neighborhood", {"address": address}),("offenders", {"address": address, "radius": radius, "page": 1, "limit": 50}),]free = address.strip().rstrip(".").lower() == FREE_TEST_ADDRESS.lower()print("# DRY RUN -- no requests sent, 0 credits used\n")print(f"# Free quota check: GET {BASE_URL}/usage?key=REDACTED")for name, params in calls:q = dict(params, key=key)print(f"\n# {name} (1 credit)")print(f'curl -G "{BASE_URL}/{name}" \\')for k, v in q.items():print(f' --data-urlencode "{k}={v}" \\')print(' -H "Accept: application/json"')print("\n# Total cost: 3 credits"+ (" (0 -- this is the free test address)" if free else "")+ "\n# Free plan = 10 credits/month, 50/day, 2 req/sec.")def main() -> int:ap = argparse.ArgumentParser(description="DoorProfit property due-diligence report")ap.add_argument("address", nargs="?", default=FREE_TEST_ADDRESS,help=f"US street address (default: free test address "f"'{FREE_TEST_ADDRESS}')")ap.add_argument("--radius", type=float, default=1.0,help="offender search radius in miles (max 5, default 1)")ap.add_argument("--lat", type=float, help="query by latitude instead of address")ap.add_argument("--lng", type=float, help="query by longitude instead of address")ap.add_argument("--usage-only", action="store_true",help="print plan/quota only (free endpoint)")ap.add_argument("--dry-run", action="store_true",help="print the curl commands and credit cost without calling")ap.add_argument("--no-cache", action="store_true", help="bypass the on-disk cache")ap.add_argument("--json", action="store_true", help="emit raw JSON instead of a report")args = ap.parse_args()if args.dry_run:dry_run(args.address, args.radius)return 0try:api = DoorProfit()except DoorProfitError as exc:print(f"error: {exc}", file=sys.stderr)return 2try:if args.usage_only:print(api.quota_summary())return 0cache = not args.no_cacheif args.lat is not None and args.lng is not None:kw = {"lat": args.lat, "lng": args.lng}else:kw = {"address": args.address}crime = api.crime(use_cache=cache, **kw)if not crime.get("success"):print(f"error: crime lookup failed: {json.dumps(crime)[:300]}", file=sys.stderr)return 1hood = api.neighborhood(use_cache=cache, **kw)# Offender data is optional garnish -- don't fail the whole report on it.try:offenders = api.offenders(radius=args.radius, use_cache=cache, **kw)except DoorProfitError as exc:offenders = {"success": False, "error": {"message": str(exc)}}if args.json:print(json.dumps({"crime": crime, "neighborhood": hood,"offenders": offenders}, indent=2))else:print_report(crime, hood, offenders, args.address)print(f"\nquota: {api.quota_summary()}")return 0except DoorProfitError as exc:detail = f" [{exc.code}]" if exc.code else ""print(f"error: {exc}{detail}", file=sys.stderr)if exc.out_of_quota:print("hint: quota exhausted -- upgrade at ""https://api.doorprofit.com/billing", file=sys.stderr)if exc.doc_url:print(f"docs: {exc.doc_url}", file=sys.stderr)return 1if __name__ == "__main__":sys.exit(main())
Source: write · chatcmpl-tool-bed1a0068baa9d47
Recorded tool call
{
"type": "tool_call",
"seq": 120,
"ts": "2026-10-18T02:42:30.995Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/report.py",
"content": "#!/usr/bin/env python3\n\"\"\"Property due-diligence report built on the DoorProfit API.\n\nUsage:\n export DOORPROFIT_API_KEY=your_key\n python3 report.py \"500 Adams St, Tulsa, OK\"\n python3 report.py --usage-only # free, never touches quota\n python3 report.py --dry-run \"...\" # show requests, spend 0 credits\n python3 report.py --json \"...\" # machine-readable output\n\nCost: one full report = 3 API credits (crime + neighborhood + offenders).\nThe free plan is 10 credits/month, so results are cached on disk and re-runs\nagainst the same address are free. The documented test address\n\"500 Main St, Dallas, TX 75202\" never consumes quota.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport sys\nimport urllib.parse\n\nfrom doorprofit import (BASE_URL, FREE_TEST_ADDRESS, DoorProfit, DoorProfitError)\n\nGRADE_SCALE = [\"A+\", \"A\", \"A-\", \"B+\", \"B\", \"B-\", \"C+\", \"C\", \"C-\",\n \"D+\", \"D\", \"D-\", \"F\"]\n\n\ndef bar(value: float, width: int = 20) -> str:\n \"\"\"Render a 0..1 score as a little bar; lower is safer.\"\"\"\n filled = max(0, min(width, round(value * width)))\n return \"[\" + \"#\" * filled + \".\" * (width - filled) + \"]\"\n\n\ndef fmt_money(n) -> str:\n return f\"${n:,.0f}\" if isinstance(n, (int, float)) else \"n/a\"\n\n\ndef print_report(crime: dict, hood: dict, offenders: dict, address: str) -> None:\n loc = crime.get(\"location\") or hood.get(\"location\") or {}\n where = loc.get(\"address\") or address\n\n print(\"=\" * 68)\n print(f\" DoorProfit due diligence: {where}\")\n if loc.get(\"lat\") is not None:\n print(f\" {loc.get('lat')}, {loc.get('lng')} | \"\n f\"{loc.get('city', '')}, {loc.get('state', '')} \"\n f\"{loc.get('zipcode', '')}\".rstrip())\n print(\"=\" * 68)\n\n # ---- Crime -----------------------------------------------------------\n grade = crime.get(\"crime_score\", \"?\")\n numeric = crime.get(\"crime_numeric\")\n print(\"\\nSAFETY\")\n idx = GRADE_SCALE.index(grade) if grade in GRADE_SCALE else None\n rank = f\"{idx + 1}/{len(GRADE_SCALE)} (A+ safest)\" if idx is not None else \"\"\n print(f\" Grade {grade} {rank}\")\n if numeric is not None:\n print(f\" Crime score {numeric:.2f} / 1.00 {bar(numeric)} (lower = safer)\")\n print(f\" Source {crime.get('crime_score_source', 'n/a')}\")\n print(f\" Description {crime.get('crime_description', 'n/a')}\")\n\n incidents = crime.get(\"incidents\")\n if incidents:\n print(f\"\\n INCIDENTS within {incidents.get('radius_feet', '?')} ft, \"\n f\"{incidents.get('date_from', '?')} -> {incidents.get('date_to', '?')}\")\n print(f\" Total {incidents.get('count', 0)}\")\n by_type = incidents.get(\"by_type\") or {}\n top = sorted(by_type.items(), key=lambda kv: kv[1], reverse=True)\n for name, count in top:\n if count:\n print(f\" {name.replace('_', ' '):<18}{count:>5}\")\n zeroed = [n for n, c in by_type.items() if not c]\n if zeroed:\n print(f\" none reported: {', '.join(z.replace('_', ' ') for z in zeroed)}\")\n else:\n print(\"\\n INCIDENTS not published for this city (incidents: null)\")\n\n # ---- Neighborhood ----------------------------------------------------\n nb = hood.get(\"neighborhood\") or {}\n if nb:\n demo = nb.get(\"demographics\") or {}\n income = nb.get(\"income\") or {}\n housing = nb.get(\"housing\") or {}\n rent = nb.get(\"rent\") or {}\n edu = nb.get(\"education\") or {}\n col = nb.get(\"cost_of_living\") or {}\n risk = ((nb.get(\"weather\") or {}).get(\"natural_disaster_risk\") or {})\n\n print(\"\\nNEIGHBORHOOD\")\n print(f\" Name {nb.get('name', 'n/a')} ({nb.get('type', '?')})\")\n print(f\" Population {demo.get('population', 'n/a'):,} \"\n f\"| density {demo.get('population_density', 'n/a')}/sq mi \"\n f\"| median age {demo.get('median_age', 'n/a')}\"\n if isinstance(demo.get(\"population\"), (int, float)) else\n f\" Population {demo.get('population', 'n/a')}\")\n print(f\" Median income {fmt_money(income.get('median'))}\")\n print(f\" Median home {fmt_money(housing.get('median_home_value'))}\")\n print(f\" Median rent {fmt_money(rent.get('median'))}/mo\", end=\"\")\n by_bed = rent.get(\"by_bedroom\") or {}\n if by_bed:\n print(\" (\" + \", \".join(f\"{k} {fmt_money(v)}\" for k, v in by_bed.items()) + \")\")\n else:\n print()\n print(f\" Bachelor's+ {edu.get('bachelors_pct', 'n/a')}% \"\n f\"| grad {edu.get('graduate_pct', 'n/a')}%\")\n print(f\" Cost of living {col.get('overall_index', 'n/a')} (100 = US avg)\")\n if risk:\n print(f\" Disaster risk overall {risk.get('overall_index', 'n/a')}\"\n f\" tornado {risk.get('tornado', 'n/a')}\"\n f\" hurricane {risk.get('hurricane', 'n/a')}\"\n f\" earthquake {risk.get('earthquake', 'n/a')}\")\n\n # ---- Offenders -------------------------------------------------------\n print(\"\\nREGISTERED OFFENDERS\")\n if offenders.get(\"success\") is False:\n print(f\" {(offenders.get('error') or {}).get('message', 'unavailable')}\")\n else:\n count = offenders.get(\"offenders_count\")\n radius = offenders.get(\"radius_miles\") or offenders.get(\"radius\") or 1\n print(f\" Within {radius} mi {count if count is not None else 'n/a'} record(s)\")\n for o in (offenders.get(\"offenders\") or [])[:5]:\n name = o.get(\"name\") or \" \".join(\n filter(None, [o.get(\"first_name\"), o.get(\"last_name\")])) or \"?\"\n dist = o.get(\"distance_miles\", o.get(\"distance\"))\n line = f\" - {name}\"\n if o.get(\"offense\"):\n line += f\" | {o['offense']}\"\n if dist is not None:\n line += f\" | {dist} mi\"\n print(line)\n remaining = (count or 0) - len(offenders.get(\"offenders\") or [])\n if remaining > 0:\n print(f\" ... and {remaining} more (paginate with page=/limit=)\")\n\n print(\"\\n\" + \"=\" * 68)\n print(\" Data: DoorProfit (https://www.doorprofit.com). \"\n \"Verify before making decisions.\")\n print(\"=\" * 68)\n\n\ndef dry_run(address: str, radius: float) -> None:\n \"\"\"Print exactly what would be sent, and what it would cost. Spends nothing.\"\"\"\n key = \"YOUR_API_KEY\"\n calls = [\n (\"crime\", {\"address\": address}),\n (\"neighborhood\", {\"address\": address}),\n (\"offenders\", {\"address\": address, \"radius\": radius, \"page\": 1, \"limit\": 50}),\n ]\n free = address.strip().rstrip(\".\").lower() == FREE_TEST_ADDRESS.lower()\n print(\"# DRY RUN -- no requests sent, 0 credits used\\n\")\n print(f\"# Free quota check: GET {BASE_URL}/usage?key=REDACTED\")\n for name, params in calls:\n q = dict(params, key=key)\n print(f\"\\n# {name} (1 credit)\")\n print(f'curl -G \"{BASE_URL}/{name}\" \\\\')\n for k, v in q.items():\n print(f' --data-urlencode \"{k}={v}\" \\\\')\n print(' -H \"Accept: application/json\"')\n print(\"\\n# Total cost: 3 credits\"\n + (\" (0 -- this is the free test address)\" if free else \"\")\n + \"\\n# Free plan = 10 credits/month, 50/day, 2 req/sec.\")\n\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"DoorProfit property due-diligence report\")\n ap.add_argument(\"address\", nargs=\"?\", default=FREE_TEST_ADDRESS,\n help=f\"US street address (default: free test address \"\n f\"'{FREE_TEST_ADDRESS}')\")\n ap.add_argument(\"--radius\", type=float, default=1.0,\n help=\"offender search radius in miles (max 5, default 1)\")\n ap.add_argument(\"--lat\", type=float, help=\"query by latitude instead of address\")\n ap.add_argument(\"--lng\", type=float, help=\"query by longitude instead of address\")\n ap.add_argument(\"--usage-only\", action=\"store_true\",\n help=\"print plan/quota only (free endpoint)\")\n ap.add_argument(\"--dry-run\", action=\"store_true\",\n help=\"print the curl commands and credit cost without calling\")\n ap.add_argument(\"--no-cache\", action=\"store_true\", help=\"bypass the on-disk cache\")\n ap.add_argument(\"--json\", action=\"store_true\", help=\"emit raw JSON instead of a report\")\n args = ap.parse_args()\n\n if args.dry_run:\n dry_run(args.address, args.radius)\n return 0\n\n try:\n api = DoorProfit()\n except DoorProfitError as exc:\n print(f\"error: {exc}\", file=sys.stderr)\n return 2\n\n try:\n if args.usage_only:\n print(api.quota_summary())\n return 0\n\n cache = not args.no_cache\n if args.lat is not None and args.lng is not None:\n kw = {\"lat\": args.lat, \"lng\": args.lng}\n else:\n kw = {\"address\": args.address}\n\n crime = api.crime(use_cache=cache, **kw)\n if not crime.get(\"success\"):\n print(f\"error: crime lookup failed: {json.dumps(crime)[:300]}\", file=sys.stderr)\n return 1\n\n hood = api.neighborhood(use_cache=cache, **kw)\n # Offender data is optional garnish -- don't fail the whole report on it.\n try:\n offenders = api.offenders(radius=args.radius, use_cache=cache, **kw)\n except DoorProfitError as exc:\n offenders = {\"success\": False, \"error\": {\"message\": str(exc)}}\n\n if args.json:\n print(json.dumps({\"crime\": crime, \"neighborhood\": hood,\n \"offenders\": offenders}, indent=2))\n else:\n print_report(crime, hood, offenders, args.address)\n print(f\"\\nquota: {api.quota_summary()}\")\n return 0\n\n except DoorProfitError as exc:\n detail = f\" [{exc.code}]\" if exc.code else \"\"\n print(f\"error: {exc}{detail}\", file=sys.stderr)\n if exc.out_of_quota:\n print(\"hint: quota exhausted -- upgrade at \"\n \"https://api.doorprofit.com/billing\", file=sys.stderr)\n if exc.doc_url:\n print(f\"docs: {exc.doc_url}\", file=sys.stderr)\n return 1\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bed1a0068baa9d47",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 121,
"ts": "2026-10-18T02:42:30.995Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 10244 bytes to /sandbox/repo/doorprofit-example/report.py",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bed1a0068baa9d47",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Next up is the Node version, MCP config, widget HTML, .env.example, and the README. Let's keep the Node one compact.
Wait, if we're going to "keep it lightweight"—maybe we can omit the Node version? But the Node version has value, because the hero snippet on the homepage suggested a JS SDK (which doesn't actually exist). Let's include a small dependency-free Node script that demonstrates the actual (fetch-based) integration, and note that the doorprofit npm package doesn't exist. It's small, so let's keep it.
Alright, let's write these files.
File edits
1 file
Writtendoorprofit-example/report.mjsView changes
/sandbox/repo/doorprofit-example/report.mjs
Written content · previous contents were not recorded
#!/usr/bin/env node/*** DoorProfit API -- dependency-free Node example (Node 18+ for global fetch).** NOTE: the `import DoorProfit from 'doorprofit'` snippet on the DoorProfit* homepage is illustrative -- no `doorprofit` package exists on npm or PyPI.* The real integration is plain HTTPS GETs, like this.** DOORPROFIT_API_KEY=your_key node report.mjs "500 Adams St, Tulsa, OK"* node report.mjs --usage-only # free, never touches quota** Each data endpoint costs 1 credit; the free plan is 10 credits/month.* The address "500 Main St, Dallas, TX 75202" is a free test address.*/const BASE_URL = 'https://api.doorprofit.com/v1';const FREE_TEST_ADDRESS = '500 Main St, Dallas, TX 75202';const API_KEY = process.env.DOORPROFIT_API_KEY;class DoorProfitError extends Error {constructor(message, { code, status } = {}) {super(message);this.code = code;this.status = status;}}async function get(path, params, retries = 3) {const url = new URL(`${BASE_URL}/${path}`);for (const [k, v] of Object.entries(params)) {if (v !== undefined && v !== null) url.searchParams.set(k, v);}url.searchParams.set('key', API_KEY);for (let attempt = 1; ; attempt++) {const res = await fetch(url, {headers: { Accept: 'application/json', 'User-Agent': 'doorprofit-example/1.0' },});const body = await res.json().catch(() => ({}));if (res.ok) return { data: body, headers: res.headers };const err = body.error || {};// 429 = slow down and retry; quota exhaustion is terminal.if (res.status === 429 && err.code === 'RATE_LIMIT_EXCEEDED' && attempt <= retries) {await new Promise((r) => setTimeout(r, Math.min(2 ** attempt * 1000, 8000)));continue;}throw new DoorProfitError(err.message || `HTTP ${res.status}`, {code: err.code,status: res.status,});}}const location = (address, lat, lng) =>address ? { address } : lat != null && lng != null ? { lat, lng } : (() => {throw new DoorProfitError('Provide either address or lat+lng');})();const crime = (loc) => get('crime', loc);const neighborhood = (loc) => get('neighborhood', loc);const offenders = (loc, radius = 1) => get('offenders', { ...loc, radius, limit: 50 });const usage = () => get('usage', {});async function main() {const argv = process.argv.slice(2);const usageOnly = argv.includes('--usage-only');const address = argv.find((a) => !a.startsWith('--')) || FREE_TEST_ADDRESS;if (!API_KEY) {console.error('error: DOORPROFIT_API_KEY is not set.');console.error('Get a free key at https://api.doorprofit.com/signup/');process.exit(2);}try {if (usageOnly) {const { data } = await usage();const d = data.data || {};console.log(`plan=${d.plan?.name} $${d.plan?.price_monthly}/mo ` +`month=${d.monthly?.used}/${d.monthly?.limit} ` +`today=${d.daily?.used}/${d.daily?.limit} ` +`rate=${d.rate_limit?.requests_per_second} req/s`,);return;}const loc = location(address);// Sequential on purpose: free plan allows 2 req/sec.const { data: c } = await crime(loc);const { data: n } = await neighborhood(loc);let o = { success: false, error: { message: 'skipped' } };try {o = (await offenders(loc)).data;} catch (e) {o = { success: false, error: { message: e.message } };}const where = c.location?.address || address;console.log(`\n=== ${where} ===`);console.log(`Safety grade : ${c.crime_score} (score ${c.crime_numeric}, lower = safer)`);console.log(`Description : ${c.crime_description}`);console.log(`Score source : ${c.crime_score_source}`);if (c.incidents) {const top = Object.entries(c.incidents.by_type || {}).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]).slice(0, 5);console.log(`Incidents : ${c.incidents.count} within ${c.incidents.radius_feet} ft ` +`(${c.incidents.date_from} -> ${c.incidents.date_to})`,);if (top.length) console.log(` top types : ${top.map(([k, v]) => `${k}=${v}`).join(', ')}`);} else {console.log('Incidents : not published for this city');}const nb = n.neighborhood || {};console.log(`Neighborhood : ${nb.name || 'n/a'}`);console.log(`Population : ${nb.demographics?.population ?? 'n/a'}`);console.log(`Median income : $${nb.income?.median ?? 'n/a'}`);console.log(`Median home : $${nb.housing?.median_home_value ?? 'n/a'}`);console.log(`Median rent : $${nb.rent?.median ?? 'n/a'}/mo`);console.log(`Cost of living : ${nb.cost_of_living?.overall_index ?? 'n/a'} (100 = US avg)`);console.log(`Disaster risk : ${nb.weather?.natural_disaster_risk?.overall_index ?? 'n/a'} (100 = US avg)`,);console.log(`Offenders : ${o.offenders_count ?? 'n/a'} within ${o.radius_miles ?? 1} mi`);for (const off of (o.offenders || []).slice(0, 3)) {console.log(` - ${off.name || '?'} | ${off.offense || '?'} | ${off.distance_miles ?? '?'} mi`);}const { data: u } = await usage();const m = u.data?.monthly || {};console.log(`\nquota : ${m.used}/${m.limit} used this month (${m.remaining} left)`);} catch (err) {console.error(`error: ${err.message}${err.code ? ` [${err.code}]` : ''}`);if (['QUOTA_EXCEEDED', 'DAILY_LIMIT_EXCEEDED'].includes(err.code)) {console.error('hint: quota exhausted -- upgrade at https://api.doorprofit.com/billing');}process.exit(1);}}main();
Source: write · chatcmpl-tool-b4e99e0797c5eb80
Recorded tool call
{
"type": "tool_call",
"seq": 124,
"ts": "2026-10-18T02:42:49.040Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/report.mjs",
"content": "#!/usr/bin/env node\n/**\n * DoorProfit API -- dependency-free Node example (Node 18+ for global fetch).\n *\n * NOTE: the `import DoorProfit from 'doorprofit'` snippet on the DoorProfit\n * homepage is illustrative -- no `doorprofit` package exists on npm or PyPI.\n * The real integration is plain HTTPS GETs, like this.\n *\n * DOORPROFIT_API_KEY=your_key node report.mjs \"500 Adams St, Tulsa, OK\"\n * node report.mjs --usage-only # free, never touches quota\n *\n * Each data endpoint costs 1 credit; the free plan is 10 credits/month.\n * The address \"500 Main St, Dallas, TX 75202\" is a free test address.\n */\n\nconst BASE_URL = 'https://api.doorprofit.com/v1';\nconst FREE_TEST_ADDRESS = '500 Main St, Dallas, TX 75202';\n\nconst API_KEY = process.env.DOORPROFIT_API_KEY;\n\nclass DoorProfitError extends Error {\n constructor(message, { code, status } = {}) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n\nasync function get(path, params, retries = 3) {\n const url = new URL(`${BASE_URL}/${path}`);\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null) url.searchParams.set(k, v);\n }\n url.searchParams.set('key', API_KEY);\n\n for (let attempt = 1; ; attempt++) {\n const res = await fetch(url, {\n headers: { Accept: 'application/json', 'User-Agent': 'doorprofit-example/1.0' },\n });\n const body = await res.json().catch(() => ({}));\n\n if (res.ok) return { data: body, headers: res.headers };\n\n const err = body.error || {};\n // 429 = slow down and retry; quota exhaustion is terminal.\n if (res.status === 429 && err.code === 'RATE_LIMIT_EXCEEDED' && attempt <= retries) {\n await new Promise((r) => setTimeout(r, Math.min(2 ** attempt * 1000, 8000)));\n continue;\n }\n throw new DoorProfitError(err.message || `HTTP ${res.status}`, {\n code: err.code,\n status: res.status,\n });\n }\n}\n\nconst location = (address, lat, lng) =>\n address ? { address } : lat != null && lng != null ? { lat, lng } : (() => {\n throw new DoorProfitError('Provide either address or lat+lng');\n })();\n\nconst crime = (loc) => get('crime', loc);\nconst neighborhood = (loc) => get('neighborhood', loc);\nconst offenders = (loc, radius = 1) => get('offenders', { ...loc, radius, limit: 50 });\nconst usage = () => get('usage', {});\n\nasync function main() {\n const argv = process.argv.slice(2);\n const usageOnly = argv.includes('--usage-only');\n const address = argv.find((a) => !a.startsWith('--')) || FREE_TEST_ADDRESS;\n\n if (!API_KEY) {\n console.error('error: DOORPROFIT_API_KEY is not set.');\n console.error('Get a free key at https://api.doorprofit.com/signup/');\n process.exit(2);\n }\n\n try {\n if (usageOnly) {\n const { data } = await usage();\n const d = data.data || {};\n console.log(\n `plan=${d.plan?.name} $${d.plan?.price_monthly}/mo ` +\n `month=${d.monthly?.used}/${d.monthly?.limit} ` +\n `today=${d.daily?.used}/${d.daily?.limit} ` +\n `rate=${d.rate_limit?.requests_per_second} req/s`,\n );\n return;\n }\n\n const loc = location(address);\n\n // Sequential on purpose: free plan allows 2 req/sec.\n const { data: c } = await crime(loc);\n const { data: n } = await neighborhood(loc);\n let o = { success: false, error: { message: 'skipped' } };\n try {\n o = (await offenders(loc)).data;\n } catch (e) {\n o = { success: false, error: { message: e.message } };\n }\n\n const where = c.location?.address || address;\n console.log(`\\n=== ${where} ===`);\n console.log(`Safety grade : ${c.crime_score} (score ${c.crime_numeric}, lower = safer)`);\n console.log(`Description : ${c.crime_description}`);\n console.log(`Score source : ${c.crime_score_source}`);\n\n if (c.incidents) {\n const top = Object.entries(c.incidents.by_type || {})\n .filter(([, v]) => v > 0)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n console.log(\n `Incidents : ${c.incidents.count} within ${c.incidents.radius_feet} ft ` +\n `(${c.incidents.date_from} -> ${c.incidents.date_to})`,\n );\n if (top.length) console.log(` top types : ${top.map(([k, v]) => `${k}=${v}`).join(', ')}`);\n } else {\n console.log('Incidents : not published for this city');\n }\n\n const nb = n.neighborhood || {};\n console.log(`Neighborhood : ${nb.name || 'n/a'}`);\n console.log(`Population : ${nb.demographics?.population ?? 'n/a'}`);\n console.log(`Median income : $${nb.income?.median ?? 'n/a'}`);\n console.log(`Median home : $${nb.housing?.median_home_value ?? 'n/a'}`);\n console.log(`Median rent : $${nb.rent?.median ?? 'n/a'}/mo`);\n console.log(`Cost of living : ${nb.cost_of_living?.overall_index ?? 'n/a'} (100 = US avg)`);\n console.log(\n `Disaster risk : ${nb.weather?.natural_disaster_risk?.overall_index ?? 'n/a'} (100 = US avg)`,\n );\n\n console.log(`Offenders : ${o.offenders_count ?? 'n/a'} within ${o.radius_miles ?? 1} mi`);\n for (const off of (o.offenders || []).slice(0, 3)) {\n console.log(` - ${off.name || '?'} | ${off.offense || '?'} | ${off.distance_miles ?? '?'} mi`);\n }\n\n const { data: u } = await usage();\n const m = u.data?.monthly || {};\n console.log(`\\nquota : ${m.used}/${m.limit} used this month (${m.remaining} left)`);\n } catch (err) {\n console.error(`error: ${err.message}${err.code ? ` [${err.code}]` : ''}`);\n if (['QUOTA_EXCEEDED', 'DAILY_LIMIT_EXCEEDED'].includes(err.code)) {\n console.error('hint: quota exhausted -- upgrade at https://api.doorprofit.com/billing');\n }\n process.exit(1);\n }\n}\n\nmain();\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b4e99e0797c5eb80",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 125,
"ts": "2026-10-18T02:42:49.040Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 5680 bytes to /sandbox/repo/doorprofit-example/report.mjs",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b4e99e0797c5eb80",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
From here on: MCP configuration, mcp_probe.py (a small script that communicates with the hosted MCP server — this is the part that's actually executable as an agent), widget HTML, .env.example, README.
I'll write a small MCP client script that initializes, retrieves the tool list, and optionally calls get_api_usage. This is genuinely useful and lightweight.
File edits
1 file
Writtendoorprofit-example/mcp_probe.pyView changes
/sandbox/repo/doorprofit-example/mcp_probe.py
Written content · previous contents were not recorded
#!/usr/bin/env python3"""Talk to the *hosted* DoorProfit MCP server -- no local process to run.DoorProfit runs its MCP server for you at https://api.doorprofit.com/mcp,so an AI agent (Claude, ChatGPT, Cursor) can query it directly with no code.This script is the same thing over plain HTTP, useful for smoke-testing a keybefore wiring it into a client.python3 mcp_probe.py --list # no key neededDOORPROFIT_API_KEY=key python3 mcp_probe.py --usage # free tool callDOORPROFIT_API_KEY=key python3 mcp_probe.py --crime "500 Adams St, Tulsa, OK"Auth: `Authorization: Bearer <key>` (or OAuth in a real MCP client).Metering: get_crime_data / get_neighborhood_data / search_offenders each cost1 API credit, exactly like the REST endpoints. get_api_usage is free."""from __future__ import annotationsimport argparseimport jsonimport osimport sysimport urllib.errorimport urllib.requestMCP_URL = "https://api.doorprofit.com/mcp"def rpc(method: str, params: dict | None = None, api_key: str | None = None,req_id: int = 1) -> dict:payload = {"jsonrpc": "2.0","id": req_id,"method": method,"params": params or {},}headers = {"Content-Type": "application/json",# The server may answer with either JSON or an SSE stream."Accept": "application/json, text/event-stream","User-Agent": "doorprofit-mcp-probe/1.0",}if api_key:headers["Authorization"] = f"Bearer {api_key}"req = urllib.request.Request(MCP_URL, data=json.dumps(payload).encode(), headers=headers, method="POST")try:with urllib.request.urlopen(req, timeout=30) as resp:raw = resp.read().decode("utf-8")except urllib.error.HTTPError as exc:raw = exc.read().decode("utf-8", "replace")try:body = json.loads(raw)msg = body.get("error", {}).get("message", raw[:200])except json.JSONDecodeError:msg = raw[:200]return {"error": {"message": f"HTTP {exc.code}: {msg}"}}# Unwrap an SSE frame ("event: message\ndata: {...}") if we got one.if raw.lstrip().startswith("event:") or "\ndata:" in raw:raw = next((l[5:].strip() for l in raw.splitlines()if l.startswith("data:")), raw)try:return json.loads(raw)except json.JSONDecodeError:return {"error": {"message": f"Unparseable response: {raw[:200]}"}}def text_of(result: dict) -> str:"""Pull the human-readable text out of an MCP tools/call result."""out = []for item in (result.get("result") or {}).get("content") or []:if item.get("type") == "text":out.append(item.get("text", ""))else:out.append(json.dumps(item, indent=2))return "\n".join(out) or json.dumps(result, indent=2)def main() -> int:ap = argparse.ArgumentParser(description="Probe the hosted DoorProfit MCP server")ap.add_argument("--list", action="store_true", help="list tools (no key required)")ap.add_argument("--usage", action="store_true", help="call get_api_usage (free)")ap.add_argument("--crime", metavar="ADDRESS", help="call get_crime_data (1 credit)")ap.add_argument("--neighborhood", metavar="ADDRESS",help="call get_neighborhood_data (1 credit)")args = ap.parse_args()key = os.environ.get("DOORPROFIT_API_KEY", "")needs_key = args.usage or args.crime or args.neighborhoodif needs_key and not key:print("error: DOORPROFIT_API_KEY is not set ""(free key: https://api.doorprofit.com/signup/)", file=sys.stderr)return 2init = rpc("initialize", {"protocolVersion": "2024-11-05","capabilities": {},"clientInfo": {"name": "doorprofit-mcp-probe", "version": "1.0"},}, api_key=key or None)if "error" in init:print(f"error: initialize failed: {init['error']['message']}", file=sys.stderr)return 1info = (init.get("result") or {}).get("serverInfo") or {}print(f"connected: {info.get('title', '?')} v{info.get('version', '?')} @ {MCP_URL}")if args.list or not needs_key:tools = rpc("tools/list", {}, api_key=key or None, req_id=2)if "error" in tools:print(f"error: {tools['error']['message']}", file=sys.stderr)return 1print("\ntools:")for t in (tools.get("result") or {}).get("tools") or []:props = list(((t.get("inputSchema") or {}).get("properties") or {}).keys())print(f" - {t['name']}({', '.join(props)})")desc = (t.get("description") or "").strip().split("\n")[0]print(f" {desc[:150]}")if not needs_key:return 0calls = []if args.usage:calls.append(("get_api_usage", {}))if args.crime:calls.append(("get_crime_data", {"address": args.crime}))if args.neighborhood:calls.append(("get_neighborhood_data", {"address": args.neighborhood}))for i, (name, arguments) in enumerate(calls, start=3):print(f"\n--- {name} {json.dumps(arguments) if arguments else ''} ---")res = rpc("tools/call", {"name": name, "arguments": arguments},api_key=key, req_id=i)if "error" in res:print(f"error: {res['error']['message']}", file=sys.stderr)return 1result = res.get("result") or {}if result.get("isError"):print("tool error:", text_of(res), file=sys.stderr)return 1print(text_of(res))return 0if __name__ == "__main__":sys.exit(main())
Source: write · chatcmpl-tool-868bb71f6a5f8762
Recorded tool call
{
"type": "tool_call",
"seq": 128,
"ts": "2026-10-18T02:43:07.175Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/mcp_probe.py",
"content": "#!/usr/bin/env python3\n\"\"\"Talk to the *hosted* DoorProfit MCP server -- no local process to run.\n\nDoorProfit runs its MCP server for you at https://api.doorprofit.com/mcp,\nso an AI agent (Claude, ChatGPT, Cursor) can query it directly with no code.\nThis script is the same thing over plain HTTP, useful for smoke-testing a key\nbefore wiring it into a client.\n\n python3 mcp_probe.py --list # no key needed\n DOORPROFIT_API_KEY=key python3 mcp_probe.py --usage # free tool call\n DOORPROFIT_API_KEY=key python3 mcp_probe.py --crime \"500 Adams St, Tulsa, OK\"\n\nAuth: `Authorization: Bearer <key>` (or OAuth in a real MCP client).\nMetering: get_crime_data / get_neighborhood_data / search_offenders each cost\n1 API credit, exactly like the REST endpoints. get_api_usage is free.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport sys\nimport urllib.error\nimport urllib.request\n\nMCP_URL = \"https://api.doorprofit.com/mcp\"\n\n\ndef rpc(method: str, params: dict | None = None, api_key: str | None = None,\n req_id: int = 1) -> dict:\n payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": req_id,\n \"method\": method,\n \"params\": params or {},\n }\n headers = {\n \"Content-Type\": \"application/json\",\n # The server may answer with either JSON or an SSE stream.\n \"Accept\": \"application/json, text/event-stream\",\n \"User-Agent\": \"doorprofit-mcp-probe/1.0\",\n }\n if api_key:\n headers[\"Authorization\"] = f\"Bearer {api_key}\"\n\n req = urllib.request.Request(\n MCP_URL, data=json.dumps(payload).encode(), headers=headers, method=\"POST\"\n )\n try:\n with urllib.request.urlopen(req, timeout=30) as resp:\n raw = resp.read().decode(\"utf-8\")\n except urllib.error.HTTPError as exc:\n raw = exc.read().decode(\"utf-8\", \"replace\")\n try:\n body = json.loads(raw)\n msg = body.get(\"error\", {}).get(\"message\", raw[:200])\n except json.JSONDecodeError:\n msg = raw[:200]\n return {\"error\": {\"message\": f\"HTTP {exc.code}: {msg}\"}}\n\n # Unwrap an SSE frame (\"event: message\\ndata: {...}\") if we got one.\n if raw.lstrip().startswith(\"event:\") or \"\\ndata:\" in raw:\n raw = next((l[5:].strip() for l in raw.splitlines()\n if l.startswith(\"data:\")), raw)\n try:\n return json.loads(raw)\n except json.JSONDecodeError:\n return {\"error\": {\"message\": f\"Unparseable response: {raw[:200]}\"}}\n\n\ndef text_of(result: dict) -> str:\n \"\"\"Pull the human-readable text out of an MCP tools/call result.\"\"\"\n out = []\n for item in (result.get(\"result\") or {}).get(\"content\") or []:\n if item.get(\"type\") == \"text\":\n out.append(item.get(\"text\", \"\"))\n else:\n out.append(json.dumps(item, indent=2))\n return \"\\n\".join(out) or json.dumps(result, indent=2)\n\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=\"Probe the hosted DoorProfit MCP server\")\n ap.add_argument(\"--list\", action=\"store_true\", help=\"list tools (no key required)\")\n ap.add_argument(\"--usage\", action=\"store_true\", help=\"call get_api_usage (free)\")\n ap.add_argument(\"--crime\", metavar=\"ADDRESS\", help=\"call get_crime_data (1 credit)\")\n ap.add_argument(\"--neighborhood\", metavar=\"ADDRESS\",\n help=\"call get_neighborhood_data (1 credit)\")\n args = ap.parse_args()\n\n key = os.environ.get(\"DOORPROFIT_API_KEY\", \"\")\n needs_key = args.usage or args.crime or args.neighborhood\n if needs_key and not key:\n print(\"error: DOORPROFIT_API_KEY is not set \"\n \"(free key: https://api.doorprofit.com/signup/)\", file=sys.stderr)\n return 2\n\n init = rpc(\"initialize\", {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\"name\": \"doorprofit-mcp-probe\", \"version\": \"1.0\"},\n }, api_key=key or None)\n if \"error\" in init:\n print(f\"error: initialize failed: {init['error']['message']}\", file=sys.stderr)\n return 1\n info = (init.get(\"result\") or {}).get(\"serverInfo\") or {}\n print(f\"connected: {info.get('title', '?')} v{info.get('version', '?')} @ {MCP_URL}\")\n\n if args.list or not needs_key:\n tools = rpc(\"tools/list\", {}, api_key=key or None, req_id=2)\n if \"error\" in tools:\n print(f\"error: {tools['error']['message']}\", file=sys.stderr)\n return 1\n print(\"\\ntools:\")\n for t in (tools.get(\"result\") or {}).get(\"tools\") or []:\n props = list(((t.get(\"inputSchema\") or {}).get(\"properties\") or {}).keys())\n print(f\" - {t['name']}({', '.join(props)})\")\n desc = (t.get(\"description\") or \"\").strip().split(\"\\n\")[0]\n print(f\" {desc[:150]}\")\n if not needs_key:\n return 0\n\n calls = []\n if args.usage:\n calls.append((\"get_api_usage\", {}))\n if args.crime:\n calls.append((\"get_crime_data\", {\"address\": args.crime}))\n if args.neighborhood:\n calls.append((\"get_neighborhood_data\", {\"address\": args.neighborhood}))\n\n for i, (name, arguments) in enumerate(calls, start=3):\n print(f\"\\n--- {name} {json.dumps(arguments) if arguments else ''} ---\")\n res = rpc(\"tools/call\", {\"name\": name, \"arguments\": arguments},\n api_key=key, req_id=i)\n if \"error\" in res:\n print(f\"error: {res['error']['message']}\", file=sys.stderr)\n return 1\n result = res.get(\"result\") or {}\n if result.get(\"isError\"):\n print(\"tool error:\", text_of(res), file=sys.stderr)\n return 1\n print(text_of(res))\n return 0\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-868bb71f6a5f8762",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 129,
"ts": "2026-10-18T02:43:07.175Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 5719 bytes to /sandbox/repo/doorprofit-example/mcp_probe.py",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-868bb71f6a5f8762",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
With this, we now have the MCP client configuration, widget HTML, .env.example, and README all set up.
File edits
1 file
Writtendoorprofit-example/mcp-config.jsonView changes
/sandbox/repo/doorprofit-example/mcp-config.json
Written content · previous contents were not recorded
{"_comment": "DoorProfit's MCP server is HOSTED -- nothing to install or run locally. Paste this into your MCP client (Claude Desktop, ChatGPT connector, Cursor) and replace YOUR_API_KEY. Or use OAuth and drop the headers block entirely.","mcpServers": {"doorprofit": {"url": "https://api.doorprofit.com/mcp","headers": {"Authorization": "Bearer YOUR_API_KEY"}}}}
Source: write · chatcmpl-tool-9352074587893bc5
Recorded tool call
{
"type": "tool_call",
"seq": 132,
"ts": "2026-10-18T02:43:19.138Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/mcp-config.json",
"content": "{\n \"_comment\": \"DoorProfit's MCP server is HOSTED -- nothing to install or run locally. Paste this into your MCP client (Claude Desktop, ChatGPT connector, Cursor) and replace YOUR_API_KEY. Or use OAuth and drop the headers block entirely.\",\n \"mcpServers\": {\n \"doorprofit\": {\n \"url\": \"https://api.doorprofit.com/mcp\",\n \"headers\": {\n \"Authorization\": \"Bearer YOUR_API_KEY\"\n }\n }\n }\n}\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9352074587893bc5",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 134,
"ts": "2026-10-18T02:43:19.138Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 414 bytes to /sandbox/repo/doorprofit-example/mcp-config.json",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9352074587893bc5",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtendoorprofit-example/crime-map-widget.htmlView changes
/sandbox/repo/doorprofit-example/crime-map-widget.html
Written content · previous contents were not recorded
<!doctype html><!--DoorProfit embeddable crime-map widget.This is the cheapest way to display DoorProfit data: widget page-views drawfrom a SEPARATE "embed views" quota (5,000 views/month free on every plan),not your API-call quota, and map tiles are free.Metering rules worth knowing:* 1 view = 1 unique browser per calendar month per API key* bots/crawlers are excluded and never count* multiple widgets on one page count as ONE view* geocoded addresses are cached server-side -> repeat loads cost 0 API callsTwo constraints:* The "Crime data by DoorProfit" attribution link must stay visible,or the map disables itself.* Optional but recommended: lock the key to your domains in the dashboard("Allowed domains", one hostname per line, ".example.com" for wildcards).Requests from other domains get HTTP 403.Swap data-key for your own, or inject it server-side so it never ships inclient source.--><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>DoorProfit crime map widget example</title><style>body { font-family: system-ui, sans-serif; margin: 2rem auto; max-width: 720px; }h1 { font-size: 1.25rem; }</style></head><body><h1>Crime map: 500 Adams St, Tulsa, OK</h1><!-- Interactive crime heatmap. MapLibre GL, no Mapbox/Google token needed. --><div id="doorprofit-crime-map"data-address="500 Adams St, Tulsa, OK"data-key="YOUR_API_KEY"data-zoom="13"data-height="400px"></div><script src="https://api.doorprofit.com/v1/embed/crime-map.js" async></script><!-- Crawlable fallback so non-JS visitors and search engines still get a link. --><noscript><a href="https://www.doorprofit.com/crime-map/" target="_blank" rel="noopener">View crime data for this area on DoorProfit</a></noscript></body></html>
Source: write · chatcmpl-tool-98d2d6e77fd496b6
Recorded tool call
{
"type": "tool_call",
"seq": 133,
"ts": "2026-10-18T02:43:19.138Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/crime-map-widget.html",
"content": "<!doctype html>\n<!--\n DoorProfit embeddable crime-map widget.\n\n This is the cheapest way to display DoorProfit data: widget page-views draw\n from a SEPARATE \"embed views\" quota (5,000 views/month free on every plan),\n not your API-call quota, and map tiles are free.\n\n Metering rules worth knowing:\n * 1 view = 1 unique browser per calendar month per API key\n * bots/crawlers are excluded and never count\n * multiple widgets on one page count as ONE view\n * geocoded addresses are cached server-side -> repeat loads cost 0 API calls\n\n Two constraints:\n * The \"Crime data by DoorProfit\" attribution link must stay visible,\n or the map disables itself.\n * Optional but recommended: lock the key to your domains in the dashboard\n (\"Allowed domains\", one hostname per line, \".example.com\" for wildcards).\n Requests from other domains get HTTP 403.\n\n Swap data-key for your own, or inject it server-side so it never ships in\n client source.\n-->\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>DoorProfit crime map widget example</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 2rem auto; max-width: 720px; }\n h1 { font-size: 1.25rem; }\n </style>\n</head>\n<body>\n <h1>Crime map: 500 Adams St, Tulsa, OK</h1>\n\n <!-- Interactive crime heatmap. MapLibre GL, no Mapbox/Google token needed. -->\n <div id=\"doorprofit-crime-map\"\n data-address=\"500 Adams St, Tulsa, OK\"\n data-key=\"YOUR_API_KEY\"\n data-zoom=\"13\"\n data-height=\"400px\"></div>\n <script src=\"https://api.doorprofit.com/v1/embed/crime-map.js\" async></script>\n\n <!-- Crawlable fallback so non-JS visitors and search engines still get a link. -->\n <noscript>\n <a href=\"https://www.doorprofit.com/crime-map/\" target=\"_blank\" rel=\"noopener\">\n View crime data for this area on DoorProfit\n </a>\n </noscript>\n</body>\n</html>\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-98d2d6e77fd496b6",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 135,
"ts": "2026-10-18T02:43:19.138Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 1960 bytes to /sandbox/repo/doorprofit-example/crime-map-widget.html",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-98d2d6e77fd496b6",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Next up is .env.example and README. Before writing the pricing section in the README, let me try once more to find the actual pricing page — probably at api.doorprofit.com/billing (login required). Let me check if it redirects.
Let me check /billing and /dashboard.
for u in "https://api.doorprofit.com/billing/" "https://api.doorprofit.com/dashboard/" "https://api.doorprofit.com/dashboard"; do printf "%-45s " "$u"; timeout 15 curl -sS -o /tmp/b.html -w "%{http_code} %{size_download} -> %{redirect_url}\n" "$u" 2>&1|tail -1; done; echo "=== grep pricing words in billing/dashboard page ==="; grep -oiE '(starter|growth|pro|enterprise|free)[^<]{0,40}\$[0-9]+|\$[0-9]+[^<]{0,40}(month|mo)' /tmp/b.html 2>/dev/null | head -20https://api.doorprofit.com/billing/ 200 33313 ->
https://api.doorprofit.com/billing/ 200 33313 -> https://api.doorprofit.com/dashboard/ 302 0 -> https://api.doorprofit.com/login/ https://api.doorprofit.com/dashboard 302 0 -> https://api.doorprofit.com/login/ === grep pricing words in billing/dashboard page ===
Recorded tool call
{
"type": "tool_call",
"seq": 138,
"ts": "2026-10-18T02:43:25.305Z",
"name": "bash",
"turn": 1,
"input": {
"command": "for u in \"https://api.doorprofit.com/billing/\" \"https://api.doorprofit.com/dashboard/\" \"https://api.doorprofit.com/dashboard\"; do printf \"%-45s \" \"$u\"; timeout 15 curl -sS -o /tmp/b.html -w \"%{http_code} %{size_download} -> %{redirect_url}\\n\" \"$u\" 2>&1|tail -1; done; echo \"=== grep pricing words in billing/dashboard page ===\"; grep -oiE '(starter|growth|pro|enterprise|free)[^<]{0,40}\\$[0-9]+|\\$[0-9]+[^<]{0,40}(month|mo)' /tmp/b.html 2>/dev/null | head -20"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-990ba8e834c38e73",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 141,
"ts": "2026-10-18T02:43:26.587Z",
"turn": 1,
"output": {
"content": [
{
"text": "https://api.doorprofit.com/billing/ 200 33313 -> \nhttps://api.doorprofit.com/dashboard/ 302 0 -> https://api.doorprofit.com/login/\nhttps://api.doorprofit.com/dashboard 302 0 -> https://api.doorprofit.com/login/\n=== grep pricing words in billing/dashboard page ===\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-990ba8e834c38e73",
"is_error": false,
"tool_kind": "shell"
}cd /tmp && grep -oE '"price[^"]*"|[0-9]+ ?/ ?month|\$[0-9]+' dp_api.html dp_www.html dp_docs.html 2>/dev/null | sort -u | head -25
dp_api.html:$17
dp_api.html:$17 dp_api.html:$52 dp_api.html:$850 dp_docs.html:"price_monthly" dp_www.html:"price" dp_www.html:"priceCurrency" dp_www.html:$8
Recorded tool call
{
"type": "tool_call",
"seq": 139,
"ts": "2026-10-18T02:43:25.305Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && grep -oE '\"price[^\"]*\"|[0-9]+ ?/ ?month|\\$[0-9]+' dp_api.html dp_www.html dp_docs.html 2>/dev/null | sort -u | head -25"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-91773afc3369f1d3",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 140,
"ts": "2026-10-18T02:43:25.305Z",
"turn": 1,
"output": {
"content": [
{
"text": "dp_api.html:$17\ndp_api.html:$52\ndp_api.html:$850\ndp_docs.html:\"price_monthly\"\ndp_www.html:\"price\"\ndp_www.html:\"priceCurrency\"\ndp_www.html:$8\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-91773afc3369f1d3",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
/billing/ returns a 200 page of 33KB without a redirect — this might be a public pricing page! Let's try parsing it.
cd /tmp && timeout 25 curl -sSL "https://api.doorprofit.com/billing/" -o dp_billing.html && python3 - <<'EOF'
import re,html
s=open('/tmp/dp_billing.html',encoding='utf-8',errors='replace').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)
s=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)
s=re.sub(r'<[^>]+>',' ',s); s=html.unescape(s); s=re.sub(r'[ \t]+',' ',s); s=re.sub(r'\n\s*\n+','\n',s)
t=s.strip()
print(len(t)); print(t[:6000])
EOF2529
2529 Pricing - DoorProfit API DoorProfit API Home Docs Main Site Login Get Your API Key DoorProfit API Home Docs Main Site Login Get Your API Key Simple, transparent pricing Choose the plan that fits your needs. All plans include overage billing so you never hit a wall. Free $0 10 API calls / month 5,000 Embed views / month Perfect for testing and evaluation Rate limit: 2 req/sec Daily limit: 50/day All API endpoints Full documentation Email support Crime map embed widget No overage (upgrade required) Current Plan Starter $25 /mo 300 API calls / month 50,000 Embed views / month Great for small applications Rate limit: 5 req/sec Daily limit: 1,500/day All API endpoints Full documentation Priority email support Crime map embed widget $0.15/call API overage $0.50/1K embed overage Sign Up Most Popular Growth $50 /mo 600 API calls / month 200,000 Embed views / month Best value for growing apps Rate limit: 10 req/sec Daily limit: 3,000/day All API endpoints Full documentation Priority support Usage analytics Crime map embed widget $0.10/call API overage $0.50/1K embed overage Sign Up Pro $100 /mo 1,200 API calls / month 500,000 Embed views / month For high-volume applications Rate limit: 20 req/sec Daily limit: 6,000/day All API endpoints Full documentation Priority support Usage analytics Crime map embed widget $0.08/call API overage $0.50/1K embed overage Sign Up What are embed views? Every plan includes the embeddable crime map widget — add an interactive crime heatmap to any website with just two lines of HTML. Embed views are billed separately from API calls so you can put crime maps on as many pages as you need without eating into your API quota. Monthly Active Users Each unique visitor counts once per month. Repeat visits from the same browser don't increase your bill. Bots excluded Googlebot, Bingbot, SEO crawlers, and social previewers are automatically filtered. Only real humans count. Free tiles & geocoding Map tiles and cached geocodes cost nothing. Most page loads use zero API calls. 2 lines of code Paste a <div> and a <script> tag — the widget handles the rest. See the docs . Need more? Contact us at support@doorprofit.com for enterprise pricing. All plans billed monthly. Overage charges billed at $25 threshold or end of billing period. © 2026 DoorProfit API. All rights reserved. | Terms | Privacy
Recorded tool call
{
"type": "tool_call",
"seq": 144,
"ts": "2026-10-18T02:43:31.054Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && timeout 25 curl -sSL \"https://api.doorprofit.com/billing/\" -o dp_billing.html && python3 - <<'EOF'\nimport re,html\ns=open('/tmp/dp_billing.html',encoding='utf-8',errors='replace').read()\ns=re.sub(r'<script.*?</script>','',s,flags=re.S|re.I)\ns=re.sub(r'<style.*?</style>','',s,flags=re.S|re.I)\ns=re.sub(r'<[^>]+>',' ',s); s=html.unescape(s); s=re.sub(r'[ \\t]+',' ',s); s=re.sub(r'\\n\\s*\\n+','\\n',s)\nt=s.strip()\nprint(len(t)); print(t[:6000])\nEOF"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b96a5d2d77aaeccb",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 145,
"ts": "2026-10-18T02:43:32.204Z",
"turn": 1,
"output": {
"content": [
{
"text": "2529\nPricing - DoorProfit API \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n DoorProfit API\n Home \n Docs \n Main Site \n Login \n Get Your API Key \n Simple, transparent pricing \n Choose the plan that fits your needs. All plans include overage billing so you never hit a wall. \n Free \n $0 \n 10 \n API calls / month \n 5,000 \n Embed views / month \n Perfect for testing and evaluation \n Rate limit: 2 req/sec \n Daily limit: 50/day \n All API endpoints \n Full documentation \n Email support \n Crime map embed widget \n No overage (upgrade required) \n Current Plan \n Starter \n $25 /mo \n 300 \n API calls / month \n 50,000 \n Embed views / month \n Great for small applications \n Rate limit: 5 req/sec \n Daily limit: 1,500/day \n All API endpoints \n Full documentation \n Priority email support \n Crime map embed widget \n $0.15/call API overage \n $0.50/1K embed overage \n Sign Up \n Most Popular \n Growth \n $50 /mo \n 600 \n API calls / month \n 200,000 \n Embed views / month \n Best value for growing apps \n Rate limit: 10 req/sec \n Daily limit: 3,000/day \n All API endpoints \n Full documentation \n Priority support \n Usage analytics \n Crime map embed widget \n $0.10/call API overage \n $0.50/1K embed overage \n Sign Up \n Pro \n $100 /mo \n 1,200 \n API calls / month \n 500,000 \n Embed views / month \n For high-volume applications \n Rate limit: 20 req/sec \n Daily limit: 6,000/day \n All API endpoints \n Full documentation \n Priority support \n Usage analytics \n Crime map embed widget \n $0.08/call API overage \n $0.50/1K embed overage \n Sign Up \n What are embed views? \n Every plan includes the embeddable crime map widget — add an interactive crime heatmap to any website with just two lines of HTML. Embed views are billed separately from API calls so you can put crime maps on as many pages as you need without eating into your API quota. \n Monthly Active Users \n Each unique visitor counts once per month. Repeat visits from the same browser don't increase your bill. \n Bots excluded \n Googlebot, Bingbot, SEO crawlers, and social previewers are automatically filtered. Only real humans count. \n Free tiles & geocoding \n Map tiles and cached geocodes cost nothing. Most page loads use zero API calls. \n 2 lines of code \n Paste a <div> and a <script> tag — the widget handles the rest. See the docs . \n Need more? Contact us at support@doorprofit.com for enterprise pricing. \n All plans billed monthly. Overage charges billed at $25 threshold or end of billing period. \n © 2026 DoorProfit API. All rights reserved. | Terms | Privacy\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-b96a5d2d77aaeccb",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Now I've got the full text of the official pricing page (publicly available at https://api.doorprofit.com/billing/, the docs have a broken link to /pricing).
Pricing: | Plan | Price | API calls/month | Embed views/month | Rate limit | Daily limit | API overage | Embed overage | | Free | $0 | 10 | 5,000 | 2 req/sec | 50/day | none (upgrade required) | — | | Starter | $25/month | 300 | 50,000 | 5 req/sec | 1,500/day | $0.15/call | $0.50/1K | | Growth | $50/month | 600 | 200,000 | 10 req/sec | 3,000/day | $0.10/call | $0.50/1K | | Pro | $100/month | 1,200 | 500,000 | 20 req/sec | 6,000/day | $0.08/call | $0.50/1K | | Enterprise | Custom | Custom | Custom | Custom | Custom | — | — |
All plans billed monthly. Overage charges are billed at the point when they hit the $25 threshold, or at the end of the billing period. Embed views = MAU (unique visitors per browser per month), bots excluded, tiles and cached geocoding are free.
Effective cost per call: Free = $0 but capped at 10 calls. Starter = $25/300 = $0.083/call. Growth = $50/600 = $0.083/call. Pro = $100/1200 = $0.083/call. Interesting — the per-call unit price is identical across all paid plans. You're only buying rate limits and daily caps. This is a useful insight.
Alright, now let me write the README with the pricing table, run tests (dry run, no-key path, MCP probe --list), and verify everything works.
Let me write the README and .env.example, then test.
Found the real pricing page. Now the README:
File edits
1 file
Writtendoorprofit-example/.env.exampleView changes
/sandbox/repo/doorprofit-example/.env.example
Written content · previous contents were not recorded
# Copy to .env and fill in. Free key: https://api.doorprofit.com/signup/# Never commit a real key -- .gitignore already excludes .env.DOORPROFIT_API_KEY=
Source: write · chatcmpl-tool-868264a1101e036e
Recorded tool call
{
"type": "tool_call",
"seq": 149,
"ts": "2026-10-18T02:44:01.636Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/.env.example",
"content": "# Copy to .env and fill in. Free key: https://api.doorprofit.com/signup/\n# Never commit a real key -- .gitignore already excludes .env.\nDOORPROFIT_API_KEY=\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-868264a1101e036e",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 151,
"ts": "2026-10-18T02:44:01.636Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 156 bytes to /sandbox/repo/doorprofit-example/.env.example",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-868264a1101e036e",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtendoorprofit-example/README.mdView changes
/sandbox/repo/doorprofit-example/README.md
Written content · previous contents were not recorded
# DoorProfit API exampleA minimal, dependency-free example against the **hosted** DoorProfit API.Nothing to install, no local stack, no server to run — it is plain HTTPS GETsto `https://api.doorprofit.com/v1`.DoorProfit is a US location-intelligence API: crime safety grades and incidentcounts, neighborhood demographics/income/housing/rent/weather/disaster risk,and registered-offender search — all keyed by street address or lat/lng.## What is here| File | What it does || --- | --- || `doorprofit.py` | Stdlib-only Python client (`crime`, `neighborhood`, `offenders`, `usage`) with disk caching, 429 backoff, and typed errors || `report.py` | CLI that prints a property due-diligence report. Has `--dry-run` (spends 0 credits) and `--usage-only` (free) || `report.mjs` | Same thing in Node 18+ (`fetch`, no packages) || `mcp_probe.py` | Talks to DoorProfit's **hosted MCP server** — `--list` needs no key || `mcp-config.json` | Drop-in MCP client config for Claude / ChatGPT / Cursor || `crime-map-widget.html` | The embeddable crime heatmap, which bills to a separate quota || `.env.example` | Template for `DOORPROFIT_API_KEY` |## SetupThere is no SDK. The `import DoorProfit from 'doorprofit'` snippet on theDoorProfit homepage is illustrative — **no `doorprofit` package exists on npmor PyPI**. You call REST directly (or use their hosted MCP server).```bashcd doorprofit-exampleexport DOORPROFIT_API_KEY=your_key # free key: https://api.doorprofit.com/signup/python3 report.py --dry-run "500 Adams St, Tulsa, OK" # 0 credits, no key neededpython3 report.py --usage-only # free endpointpython3 report.py "500 Adams St, Tulsa, OK" # costs 3 creditspython3 report.py --json "500 Adams St, Tulsa, OK" | jq .crime.crime_scorenode report.mjs "500 Adams St, Tulsa, OK"python3 mcp_probe.py --list # no key neededDOORPROFIT_API_KEY=$DOORPROFIT_API_KEY python3 mcp_probe.py --crime "500 Adams St, Tulsa, OK"```Zero dependencies, so there is no install step. Python 3.9+ / Node 18+.## The four endpoints```GET /v1/crime safety grade A+..F, numeric score, incident counts by typeGET /v1/neighborhood demographics, income, housing, education, cost of living, rent, weather, disaster riskGET /v1/offenders registered offenders by location+radius, or by name (paginated; each page = 1 credit)GET /v1/usage your plan, quota, remaining calls -- FREE, never metered```Auth is a `key` query param on every request. Each endpoint takes either`address=` or `lat=` + `lng=`.> **Free test address:** `500 Main St, Dallas, TX 75202` is documented as never> consuming quota. Use it to validate an integration without spending credits.## How pricing worksPricing is **per-plan monthly call quota**, not per-call. Source:<https://api.doorprofit.com/billing/> (the docs' "pricing page" links point at`/pricing`, which 404s — `/billing` is the live page).| Plan | Price | API calls/mo | Daily cap | Rate limit | Embed views/mo | API overage | Embed overage || --- | --- | --- | --- | --- | --- | --- | --- || Free | $0 | **10** | 50/day | 2 req/s | 5,000 | none — hard 429 | — || Starter | $25/mo | 300 | 1,500/day | 5 req/s | 50,000 | $0.15/call | $0.50/1K || Growth | $50/mo | 600 | 3,000/day | 10 req/s | 200,000 | $0.10/call | $0.50/1K || Pro | $100/mo | 1,200 | 6,000/day | 20 req/s | 500,000 | $0.08/call | $0.50/1K || Enterprise | custom | custom | custom | custom | custom | — | — |Things that actually matter when budgeting:- **Effective price per call is ~$0.083 on every paid tier** ($25/300,$50/600, $100/1,200 all work out the same). Upgrading buys you a higher*rate limit* and *daily cap*, not cheaper data. Pick the tier byrequests/sec and burst needs, not by unit price.- **Overage is opt-in and tier-dependent.** Paid plans keep working past thecap at $0.08–$0.15/call; the Free plan has no overage and just returns 429`QUOTA_EXCEEDED`. Overage is billed once it hits $25 or at period end.- **One report = 3 credits.** Crime, neighborhood, and offenders are separatemetered calls — there is no combined endpoint. On the free plan that is**3 reports per month**. `report.py` caches to `.doorprofit-cache/` sore-running the same address costs nothing.- **Offender pagination costs extra** — each `page=` request is another credit.- **Embed views are a separate, much cheaper bucket.** The widget bills bymonthly *unique visitor* (same browser all month = 1 view), excludes bots,and map tiles plus cached geocodes are free. Displaying data on a publicpage is far cheaper via the widget than via API calls.- `/v1/usage` is free and uncounted, so you can poll it freely to self-monitor.Every response also carries `X-Monthly-Remaining`, `X-Daily-Remaining`,`X-RateLimit-Limit`, and `X-Plan` headers.## Agent / MCP workflowDoorProfit hosts an MCP server at `https://api.doorprofit.com/mcp` — there isnothing to install or run locally. Tools: `get_crime_data`,`get_neighborhood_data`, `search_offenders` (1 credit each), `get_api_usage`(free). Auth via OAuth or `Authorization: Bearer <key>`. MCP calls are meteredexactly like REST calls.See `mcp-config.json`, or run `python3 mcp_probe.py --list` (works with no keyat all — `initialize` and `tools/list` are unauthenticated).## Gotchas- Keys in query strings leak into logs. Prefer server-side calls, and for thewidget set **Allowed domains** in the dashboard so a stolen key can't renderembeds elsewhere (non-allowlisted origins get 403).- The widget's "Crime data by DoorProfit" attribution link is **mandatory** —removing it disables the map.- `incidents` is `null` in cities that don't publish incident-level data; thegrade then falls back to block-group statistics. Check`crime_score_source` (`"incidents"` vs `"block_group"`) before trusting thegranularity.- `incidents` are **counts within 1,000 ft**, over a `date_from`→`date_to`window that varies by city. There is no list of individual incidents, despitewhat some marketing copy implies.- Offender data is sensitive PII. Handle it accordingly and check the[terms](https://api.doorprofit.com/terms/) for permitted use.
Source: write · chatcmpl-tool-bea3579c35c21f54
Recorded tool call
{
"type": "tool_call",
"seq": 150,
"ts": "2026-10-18T02:44:01.636Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/README.md",
"content": "# DoorProfit API example\n\nA minimal, dependency-free example against the **hosted** DoorProfit API.\nNothing to install, no local stack, no server to run — it is plain HTTPS GETs\nto `https://api.doorprofit.com/v1`.\n\nDoorProfit is a US location-intelligence API: crime safety grades and incident\ncounts, neighborhood demographics/income/housing/rent/weather/disaster risk,\nand registered-offender search — all keyed by street address or lat/lng.\n\n## What is here\n\n| File | What it does |\n| --- | --- |\n| `doorprofit.py` | Stdlib-only Python client (`crime`, `neighborhood`, `offenders`, `usage`) with disk caching, 429 backoff, and typed errors |\n| `report.py` | CLI that prints a property due-diligence report. Has `--dry-run` (spends 0 credits) and `--usage-only` (free) |\n| `report.mjs` | Same thing in Node 18+ (`fetch`, no packages) |\n| `mcp_probe.py` | Talks to DoorProfit's **hosted MCP server** — `--list` needs no key |\n| `mcp-config.json` | Drop-in MCP client config for Claude / ChatGPT / Cursor |\n| `crime-map-widget.html` | The embeddable crime heatmap, which bills to a separate quota |\n| `.env.example` | Template for `DOORPROFIT_API_KEY` |\n\n## Setup\n\nThere is no SDK. The `import DoorProfit from 'doorprofit'` snippet on the\nDoorProfit homepage is illustrative — **no `doorprofit` package exists on npm\nor PyPI**. You call REST directly (or use their hosted MCP server).\n\n```bash\ncd doorprofit-example\nexport DOORPROFIT_API_KEY=your_key # free key: https://api.doorprofit.com/signup/\n\npython3 report.py --dry-run \"500 Adams St, Tulsa, OK\" # 0 credits, no key needed\npython3 report.py --usage-only # free endpoint\npython3 report.py \"500 Adams St, Tulsa, OK\" # costs 3 credits\npython3 report.py --json \"500 Adams St, Tulsa, OK\" | jq .crime.crime_score\n\nnode report.mjs \"500 Adams St, Tulsa, OK\"\n\npython3 mcp_probe.py --list # no key needed\nDOORPROFIT_API_KEY=$DOORPROFIT_API_KEY python3 mcp_probe.py --crime \"500 Adams St, Tulsa, OK\"\n```\n\nZero dependencies, so there is no install step. Python 3.9+ / Node 18+.\n\n## The four endpoints\n\n```\nGET /v1/crime safety grade A+..F, numeric score, incident counts by type\nGET /v1/neighborhood demographics, income, housing, education, cost of living, rent, weather, disaster risk\nGET /v1/offenders registered offenders by location+radius, or by name (paginated; each page = 1 credit)\nGET /v1/usage your plan, quota, remaining calls -- FREE, never metered\n```\n\nAuth is a `key` query param on every request. Each endpoint takes either\n`address=` or `lat=` + `lng=`.\n\n> **Free test address:** `500 Main St, Dallas, TX 75202` is documented as never\n> consuming quota. Use it to validate an integration without spending credits.\n\n## How pricing works\n\nPricing is **per-plan monthly call quota**, not per-call. Source:\n<https://api.doorprofit.com/billing/> (the docs' \"pricing page\" links point at\n`/pricing`, which 404s — `/billing` is the live page).\n\n| Plan | Price | API calls/mo | Daily cap | Rate limit | Embed views/mo | API overage | Embed overage |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| Free | $0 | **10** | 50/day | 2 req/s | 5,000 | none — hard 429 | — |\n| Starter | $25/mo | 300 | 1,500/day | 5 req/s | 50,000 | $0.15/call | $0.50/1K |\n| Growth | $50/mo | 600 | 3,000/day | 10 req/s | 200,000 | $0.10/call | $0.50/1K |\n| Pro | $100/mo | 1,200 | 6,000/day | 20 req/s | 500,000 | $0.08/call | $0.50/1K |\n| Enterprise | custom | custom | custom | custom | custom | — | — |\n\nThings that actually matter when budgeting:\n\n- **Effective price per call is ~$0.083 on every paid tier** ($25/300,\n $50/600, $100/1,200 all work out the same). Upgrading buys you a higher\n *rate limit* and *daily cap*, not cheaper data. Pick the tier by\n requests/sec and burst needs, not by unit price.\n- **Overage is opt-in and tier-dependent.** Paid plans keep working past the\n cap at $0.08–$0.15/call; the Free plan has no overage and just returns 429\n `QUOTA_EXCEEDED`. Overage is billed once it hits $25 or at period end.\n- **One report = 3 credits.** Crime, neighborhood, and offenders are separate\n metered calls — there is no combined endpoint. On the free plan that is\n **3 reports per month**. `report.py` caches to `.doorprofit-cache/` so\n re-running the same address costs nothing.\n- **Offender pagination costs extra** — each `page=` request is another credit.\n- **Embed views are a separate, much cheaper bucket.** The widget bills by\n monthly *unique visitor* (same browser all month = 1 view), excludes bots,\n and map tiles plus cached geocodes are free. Displaying data on a public\n page is far cheaper via the widget than via API calls.\n- `/v1/usage` is free and uncounted, so you can poll it freely to self-monitor.\n Every response also carries `X-Monthly-Remaining`, `X-Daily-Remaining`,\n `X-RateLimit-Limit`, and `X-Plan` headers.\n\n## Agent / MCP workflow\n\nDoorProfit hosts an MCP server at `https://api.doorprofit.com/mcp` — there is\nnothing to install or run locally. Tools: `get_crime_data`,\n`get_neighborhood_data`, `search_offenders` (1 credit each), `get_api_usage`\n(free). Auth via OAuth or `Authorization: Bearer <key>`. MCP calls are metered\nexactly like REST calls.\n\nSee `mcp-config.json`, or run `python3 mcp_probe.py --list` (works with no key\nat all — `initialize` and `tools/list` are unauthenticated).\n\n## Gotchas\n\n- Keys in query strings leak into logs. Prefer server-side calls, and for the\n widget set **Allowed domains** in the dashboard so a stolen key can't render\n embeds elsewhere (non-allowlisted origins get 403).\n- The widget's \"Crime data by DoorProfit\" attribution link is **mandatory** —\n removing it disables the map.\n- `incidents` is `null` in cities that don't publish incident-level data; the\n grade then falls back to block-group statistics. Check\n `crime_score_source` (`\"incidents\"` vs `\"block_group\"`) before trusting the\n granularity.\n- `incidents` are **counts within 1,000 ft**, over a `date_from`→`date_to`\n window that varies by city. There is no list of individual incidents, despite\n what some marketing copy implies.\n- Offender data is sensitive PII. Handle it accordingly and check the\n [terms](https://api.doorprofit.com/terms/) for permitted use.\n"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bea3579c35c21f54",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 152,
"ts": "2026-10-18T02:44:01.636Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 6280 bytes to /sandbox/repo/doorprofit-example/README.md",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-bea3579c35c21f54",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Alright, let's test everything that can be run without a key:
python3 report.py --dry-run "..."python3 report.pywithout a key → error path (exit 2)python3 mcp_probe.py --list→ works without a keynode report.mjs --usage-onlywithout a key → error path- Python syntax check
Also, should I add .doorprofit-cache and .env to .gitignore? The repo's .gitignore doesn't have .env or the cache directory. It's something to consider, but the .gitignore is at the repo root. I could add entries. Let me check whether the README says ".gitignore already excludes .env" — needs verification! The root .gitignore doesn't have .env. I'll add it (small addition, keeps the claim correct).
Let's run the tests.
cd /sandbox/repo/doorprofit-example && echo "=== compile ===" && python3 -m py_compile doorprofit.py report.py mcp_probe.py && echo OK && node --check report.mjs && echo "node OK" && echo && echo "=== dry-run (0 credits) ===" && python3 report.py --dry-run "500 Adams St, Tulsa, OK"
=== compile ===
=== compile === OK node OK === dry-run (0 credits) === # DRY RUN -- no requests sent, 0 credits used # Free quota check: GET https://api.doorprofit.com/v1/usage?key=REDACTED # crime (1 credit) curl -G "https://api.doorprofit.com/v1/crime" \ --data-urlencode "address=500 Adams St, Tulsa, OK" \ --data-urlencode "key=YOUR_API_KEY" \ -H "Accept: application/json" # neighborhood (1 credit) curl -G "https://api.doorprofit.com/v1/neighborhood" \ --data-urlencode "address=500 Adams St, Tulsa, OK" \ --data-urlencode "key=YOUR_API_KEY" \ -H "Accept: application/json" # offenders (1 credit) curl -G "https://api.doorprofit.com/v1/offenders" \ --data-urlencode "address=500 Adams St, Tulsa, OK" \ --data-urlencode "radius=1.0" \ --data-urlencode "page=1" \ --data-urlencode "limit=50" \ --data-urlencode "key=YOUR_API_KEY" \ -H "Accept: application/json" # Total cost: 3 credits # Free plan = 10 credits/month, 50/day, 2 req/sec.
Recorded tool call
{
"type": "tool_call",
"seq": 155,
"ts": "2026-10-18T02:44:07.672Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/doorprofit-example && echo \"=== compile ===\" && python3 -m py_compile doorprofit.py report.py mcp_probe.py && echo OK && node --check report.mjs && echo \"node OK\" && echo && echo \"=== dry-run (0 credits) ===\" && python3 report.py --dry-run \"500 Adams St, Tulsa, OK\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-972a4d3cd2ea5540",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 157,
"ts": "2026-10-18T02:44:07.672Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== compile ===\nOK\nnode OK\n\n=== dry-run (0 credits) ===\n# DRY RUN -- no requests sent, 0 credits used\n\n# Free quota check: GET https://api.doorprofit.com/v1/usage?key=REDACTED\n\n# crime (1 credit)\ncurl -G \"https://api.doorprofit.com/v1/crime\" \\\n --data-urlencode \"address=500 Adams St, Tulsa, OK\" \\\n --data-urlencode \"key=YOUR_API_KEY\" \\\n -H \"Accept: application/json\"\n\n# neighborhood (1 credit)\ncurl -G \"https://api.doorprofit.com/v1/neighborhood\" \\\n --data-urlencode \"address=500 Adams St, Tulsa, OK\" \\\n --data-urlencode \"key=YOUR_API_KEY\" \\\n -H \"Accept: application/json\"\n\n# offenders (1 credit)\ncurl -G \"https://api.doorprofit.com/v1/offenders\" \\\n --data-urlencode \"address=500 Adams St, Tulsa, OK\" \\\n --data-urlencode \"radius=1.0\" \\\n --data-urlencode \"page=1\" \\\n --data-urlencode \"limit=50\" \\\n --data-urlencode \"key=YOUR_API_KEY\" \\\n -H \"Accept: application/json\"\n\n# Total cost: 3 credits\n# Free plan = 10 credits/month, 50/day, 2 req/sec.\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-972a4d3cd2ea5540",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo/doorprofit-example && echo "=== no-key error path (report.py) ===" && env -u DOORPROFIT_API_KEY python3 report.py "500 Adams St, Tulsa, OK"; echo "exit=$?"; echo; echo "=== no-key error path (report.mjs) ==="; env -u DOORPROFIT_API_KEY node report.mjs "500 Adams St, Tulsa, OK"; echo "exit=$?"; echo; echo "=== bad key -> real 401 from API ==="; DOORPROFIT_API_KEY=invalid python3 report.py --usage-only; echo "exit=$?"
=== no-key error path (report.py) ===
=== no-key error path (report.py) === error: No API key. Set DOORPROFIT_API_KEY or pass api_key=... (free key: https://api.doorprofit.com/signup/) exit=2 === no-key error path (report.mjs) === error: DOORPROFIT_API_KEY is not set. Get a free key at https://api.doorprofit.com/signup/ exit=2 === bad key -> real 401 from API === error: The API key provided is invalid or inactive. [INVALID_API_KEY] docs: https://api.doorprofit.com/docs/ exit=1
Recorded tool call
{
"type": "tool_call",
"seq": 156,
"ts": "2026-10-18T02:44:07.672Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/doorprofit-example && echo \"=== no-key error path (report.py) ===\" && env -u DOORPROFIT_API_KEY python3 report.py \"500 Adams St, Tulsa, OK\"; echo \"exit=$?\"; echo; echo \"=== no-key error path (report.mjs) ===\"; env -u DOORPROFIT_API_KEY node report.mjs \"500 Adams St, Tulsa, OK\"; echo \"exit=$?\"; echo; echo \"=== bad key -> real 401 from API ===\"; DOORPROFIT_API_KEY=invalid python3 report.py --usage-only; echo \"exit=$?\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-949c61383a7094af",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 158,
"ts": "2026-10-18T02:44:08.826Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== no-key error path (report.py) ===\nerror: No API key. Set DOORPROFIT_API_KEY or pass api_key=... (free key: https://api.doorprofit.com/signup/)\nexit=2\n\n=== no-key error path (report.mjs) ===\nerror: DOORPROFIT_API_KEY is not set.\nGet a free key at https://api.doorprofit.com/signup/\nexit=2\n\n=== bad key -> real 401 from API ===\nerror: The API key provided is invalid or inactive. [INVALID_API_KEY]\ndocs: https://api.doorprofit.com/docs/\nexit=1\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-949c61383a7094af",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
All the key-not-required paths work, and the actual 401 from the live API is correctly parsed by my error handler. Next, let's test the MCP probe without a key.
Also, I'll test the print_report function with a mocked/synthetic payload to verify the rendering (since I can't call the live API). Let me inject a fixture based on the documented example response and run print_report.
I'll do both.
cd /sandbox/repo/doorprofit-example && echo "=== MCP probe --list (no key, hits hosted server) ===" && python3 mcp_probe.py --list; echo "exit=$?"
=== MCP probe --list (no key, hits hosted server) ===
=== MCP probe --list (no key, hits hosted server) ===
connected: DoorProfit Crime & Neighborhood Data v1.0.0 @ https://api.doorprofit.com/mcp
tools:
- get_crime_data(address, lat, lng)
Crime safety grade and nearby incident counts for one US address or coordinate pair. Returns a letter score (A+ safest through F), a short description
- get_neighborhood_data(address, lat, lng)
Profile of the neighborhood (or census block group) containing one US address or coordinate pair: demographics, median household income, home values,
- search_offenders(address, lat, lng, radius, first_name, last_name, dob, city, state, zipcode, page, limit)
Search US registered offenders in one of two modes. Location mode: pass address or lat+lng, plus an optional radius in miles (default 1, capped at 3);
- get_api_usage()
Report the plan, monthly and daily call usage, remaining quota, reset times, rate limit and overage status for the DoorProfit API key authenticating t
exit=0
Recorded tool call
{
"type": "tool_call",
"seq": 161,
"ts": "2026-10-18T02:44:20.094Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/doorprofit-example && echo \"=== MCP probe --list (no key, hits hosted server) ===\" && python3 mcp_probe.py --list; echo \"exit=$?\""
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-82d963af646884c3",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 164,
"ts": "2026-10-18T02:44:21.373Z",
"turn": 1,
"output": {
"content": [
{
"text": "=== MCP probe --list (no key, hits hosted server) ===\nconnected: DoorProfit Crime & Neighborhood Data v1.0.0 @ https://api.doorprofit.com/mcp\n\ntools:\n - get_crime_data(address, lat, lng)\n Crime safety grade and nearby incident counts for one US address or coordinate pair. Returns a letter score (A+ safest through F), a short description\n - get_neighborhood_data(address, lat, lng)\n Profile of the neighborhood (or census block group) containing one US address or coordinate pair: demographics, median household income, home values, \n - search_offenders(address, lat, lng, radius, first_name, last_name, dob, city, state, zipcode, page, limit)\n Search US registered offenders in one of two modes. Location mode: pass address or lat+lng, plus an optional radius in miles (default 1, capped at 3);\n - get_api_usage()\n Report the plan, monthly and daily call usage, remaining quota, reset times, rate limit and overage status for the DoorProfit API key authenticating t\nexit=0\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-82d963af646884c3",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo/doorprofit-example && cat > /tmp/render_test.py <<'PY'
# Verify report rendering using the documented example payloads (no API calls).
import json, sys
sys.path.insert(0, '.')
from report import print_report
crime = json.loads('''{"success":true,"location":{"address":"500 Main St, Dallas, TX 75202, USA","city":"Dallas","state":"TX","zipcode":"75202","county":"Dallas County","lat":32.78014,"lng":-96.800454},"crime_score":"C+","crime_numeric":0.21,"crime_score_source":"incidents","crime_description":"Higher than average crime","incidents":{"radius_feet":1000,"date_from":"2025-04-01","date_to":"2026-04-01","count":245,"by_type":{"murder":0,"shooting":3,"assault":74,"sexual_offense":7,"robbery":5,"kidnapping":1,"weapons":1,"theft":109,"burglary":5,"drug_offense":1,"vandalism":39,"arson":0}}}''')
hood = json.loads('''{"success":true,"location":{"address":"500 Main St, Dallas, TX 75202, USA","lat":32.78014,"lng":-96.800454},"neighborhood":{"name":"West End Historic District","type":"neighborhood","demographics":{"population":4280,"population_density":1200.5,"median_age":34.2},"income":{"median":52400},"housing":{"median_home_value":285000},"rent":{"median":1150,"by_bedroom":{"1br":950,"2br":1150,"3br":1400,"4br":1600}},"education":{"bachelors_pct":18.3,"graduate_pct":6.1},"cost_of_living":{"overall_index":95.2},"weather":{"natural_disaster_risk":{"overall_index":110.4,"earthquake":12.0,"hurricane":40.2,"tornado":280.1}}}}''')
off = json.loads('''{"success":true,"offenders_count":5,"radius_miles":1,"offenders":[{"name":"J*** D**","offense":"Sexual Assault (2019)","distance_miles":0.3},{"name":"A*** B**","offense":"Indecency with a child (2017)","distance_miles":0.6}]}''')
print_report(crime, hood, off, "500 Main St, Dallas, TX 75202")
print("\n\n########## incidents:null + offender failure variant ##########\n")
crime2 = dict(crime); crime2["incidents"]=None; crime2["crime_score"]="A-"; crime2["crime_numeric"]=0.08; crime2["crime_score_source"]="block_group"
print_report(crime2, {"success":True,"location":crime["location"],"neighborhood":{}}, {"success":False,"error":{"message":"not available in this state"}}, "Somewhere")
PY
python3 /tmp/render_test.py====================================================================
====================================================================
DoorProfit due diligence: 500 Main St, Dallas, TX 75202, USA
32.78014, -96.800454 | Dallas, TX 75202
====================================================================
SAFETY
Grade C+ 7/13 (A+ safest)
Crime score 0.21 / 1.00 [####................] (lower = safer)
Source incidents
Description Higher than average crime
INCIDENTS within 1000 ft, 2025-04-01 -> 2026-04-01
Total 245
theft 109
assault 74
vandalism 39
sexual offense 7
robbery 5
burglary 5
shooting 3
kidnapping 1
weapons 1
drug offense 1
none reported: murder, arson
NEIGHBORHOOD
Name West End Historic District (neighborhood)
Population 4,280 | density 1200.5/sq mi | median age 34.2
Median income $52,400
Median home $285,000
Median rent $1,150/mo (1br $950, 2br $1,150, 3br $1,400, 4br $1,600)
Bachelor's+ 18.3% | grad 6.1%
Cost of living 95.2 (100 = US avg)
Disaster risk overall 110.4 tornado 280.1 hurricane 40.2 earthquake 12.0
REGISTERED OFFENDERS
Within 1 mi 5 record(s)
- J*** D** | Sexual Assault (2019) | 0.3 mi
- A*** B** | Indecency with a child (2017) | 0.6 mi
... and 3 more (paginate with page=/limit=)
====================================================================
Data: DoorProfit (https://www.doorprofit.com). Verify before making decisions.
====================================================================
########## incidents:null + offender failure variant ##########
====================================================================
DoorProfit due diligence: 500 Main St, Dallas, TX 75202, USA
32.78014, -96.800454 | Dallas, TX 75202
====================================================================
SAFETY
Grade A- 3/13 (A+ safest)
Crime score 0.08 / 1.00 [##..................] (lower = safer)
Source block_group
Description Higher than average crime
INCIDENTS not published for this city (incidents: null)
REGISTERED OFFENDERS
not available in this state
====================================================================
Data: DoorProfit (https://www.doorprofit.com). Verify before making decisions.
====================================================================
Recorded tool call
{
"type": "tool_call",
"seq": 162,
"ts": "2026-10-18T02:44:20.094Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo/doorprofit-example && cat > /tmp/render_test.py <<'PY'\n# Verify report rendering using the documented example payloads (no API calls).\nimport json, sys\nsys.path.insert(0, '.')\nfrom report import print_report\n\ncrime = json.loads('''{\"success\":true,\"location\":{\"address\":\"500 Main St, Dallas, TX 75202, USA\",\"city\":\"Dallas\",\"state\":\"TX\",\"zipcode\":\"75202\",\"county\":\"Dallas County\",\"lat\":32.78014,\"lng\":-96.800454},\"crime_score\":\"C+\",\"crime_numeric\":0.21,\"crime_score_source\":\"incidents\",\"crime_description\":\"Higher than average crime\",\"incidents\":{\"radius_feet\":1000,\"date_from\":\"2025-04-01\",\"date_to\":\"2026-04-01\",\"count\":245,\"by_type\":{\"murder\":0,\"shooting\":3,\"assault\":74,\"sexual_offense\":7,\"robbery\":5,\"kidnapping\":1,\"weapons\":1,\"theft\":109,\"burglary\":5,\"drug_offense\":1,\"vandalism\":39,\"arson\":0}}}''')\nhood = json.loads('''{\"success\":true,\"location\":{\"address\":\"500 Main St, Dallas, TX 75202, USA\",\"lat\":32.78014,\"lng\":-96.800454},\"neighborhood\":{\"name\":\"West End Historic District\",\"type\":\"neighborhood\",\"demographics\":{\"population\":4280,\"population_density\":1200.5,\"median_age\":34.2},\"income\":{\"median\":52400},\"housing\":{\"median_home_value\":285000},\"rent\":{\"median\":1150,\"by_bedroom\":{\"1br\":950,\"2br\":1150,\"3br\":1400,\"4br\":1600}},\"education\":{\"bachelors_pct\":18.3,\"graduate_pct\":6.1},\"cost_of_living\":{\"overall_index\":95.2},\"weather\":{\"natural_disaster_risk\":{\"overall_index\":110.4,\"earthquake\":12.0,\"hurricane\":40.2,\"tornado\":280.1}}}}''')\noff = json.loads('''{\"success\":true,\"offenders_count\":5,\"radius_miles\":1,\"offenders\":[{\"name\":\"J*** D**\",\"offense\":\"Sexual Assault (2019)\",\"distance_miles\":0.3},{\"name\":\"A*** B**\",\"offense\":\"Indecency with a child (2017)\",\"distance_miles\":0.6}]}''')\nprint_report(crime, hood, off, \"500 Main St, Dallas, TX 75202\")\n\nprint(\"\\n\\n########## incidents:null + offender failure variant ##########\\n\")\ncrime2 = dict(crime); crime2[\"incidents\"]=None; crime2[\"crime_score\"]=\"A-\"; crime2[\"crime_numeric\"]=0.08; crime2[\"crime_score_source\"]=\"block_group\"\nprint_report(crime2, {\"success\":True,\"location\":crime[\"location\"],\"neighborhood\":{}}, {\"success\":False,\"error\":{\"message\":\"not available in this state\"}}, \"Somewhere\")\nPY\npython3 /tmp/render_test.py"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a92d6c7fc27c5819",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 163,
"ts": "2026-10-18T02:44:20.094Z",
"turn": 1,
"output": {
"content": [
{
"text": "====================================================================\n DoorProfit due diligence: 500 Main St, Dallas, TX 75202, USA\n 32.78014, -96.800454 | Dallas, TX 75202\n====================================================================\n\nSAFETY\n Grade C+ 7/13 (A+ safest)\n Crime score 0.21 / 1.00 [####................] (lower = safer)\n Source incidents\n Description Higher than average crime\n\n INCIDENTS within 1000 ft, 2025-04-01 -> 2026-04-01\n Total 245\n theft 109\n assault 74\n vandalism 39\n sexual offense 7\n robbery 5\n burglary 5\n shooting 3\n kidnapping 1\n weapons 1\n drug offense 1\n none reported: murder, arson\n\nNEIGHBORHOOD\n Name West End Historic District (neighborhood)\n Population 4,280 | density 1200.5/sq mi | median age 34.2\n Median income $52,400\n Median home $285,000\n Median rent $1,150/mo (1br $950, 2br $1,150, 3br $1,400, 4br $1,600)\n Bachelor's+ 18.3% | grad 6.1%\n Cost of living 95.2 (100 = US avg)\n Disaster risk overall 110.4 tornado 280.1 hurricane 40.2 earthquake 12.0\n\nREGISTERED OFFENDERS\n Within 1 mi 5 record(s)\n - J*** D** | Sexual Assault (2019) | 0.3 mi\n - A*** B** | Indecency with a child (2017) | 0.6 mi\n ... and 3 more (paginate with page=/limit=)\n\n====================================================================\n Data: DoorProfit (https://www.doorprofit.com). Verify before making decisions.\n====================================================================\n\n\n########## incidents:null + offender failure variant ##########\n\n====================================================================\n DoorProfit due diligence: 500 Main St, Dallas, TX 75202, USA\n 32.78014, -96.800454 | Dallas, TX 75202\n====================================================================\n\nSAFETY\n Grade A- 3/13 (A+ safest)\n Crime score 0.08 / 1.00 [##..................] (lower = safer)\n Source block_group\n Description Higher than average crime\n\n INCIDENTS not published for this city (incidents: null)\n\nREGISTERED OFFENDERS\n not available in this state\n\n====================================================================\n Data: DoorProfit (https://www.doorprofit.com). Verify before making decisions.\n====================================================================\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a92d6c7fc27c5819",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything is rendering correctly. One inconsistency I noticed: MCP's search_offenders states that the radius is "capped at 3," but the REST documentation says "Max: 5." A minor documentation inconsistency worth noting in the README's caveats section. I'll add it.
Also, fix the .gitignore so it excludes .env and .doorprofit-cache/ so that the README's descriptions are accurate.
Let's do both.
File edits
1 file
Modified.gitignore+6−0View changes
/sandbox/repo/.gitignore
Replacement excerpt
# Logs / OS cruft*.log.DS_Store++# Secrets & local API response caches+.env+.env.*+!.env.example+.doorprofit-cache/
Source: edit · chatcmpl-tool-9236a10e596f34a2
Recorded tool call
{
"type": "tool_call",
"seq": 167,
"ts": "2026-10-18T02:44:25.934Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/.gitignore",
"edits": [
{
"newText": "# Logs / OS cruft\n*.log\n.DS_Store\n\n# Secrets & local API response caches\n.env\n.env.*\n!.env.example\n.doorprofit-cache/",
"oldText": "# Logs / OS cruft\n*.log\n.DS_Store"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9236a10e596f34a2",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 170,
"ts": "2026-10-18T02:44:25.934Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/.gitignore.",
"type": "text"
}
],
"details": {
"diff": " ...\n 3 # Logs / OS cruft\n 4 *.log\n 5 .DS_Store\n 6 \n+ 7 # Secrets & local API response caches\n+ 8 .env\n+ 9 .env.*\n+10 !.env.example\n+11 .doorprofit-cache/\n+12 \n 7 # JavaScript / TypeScript\n 8 node_modules/\n 9 .next/\n 10 dist/\n ...",
"patch": "--- /sandbox/repo/.gitignore\n+++ /sandbox/repo/.gitignore\n@@ -3,8 +3,14 @@\n # Logs / OS cruft\n *.log\n .DS_Store\n \n+# Secrets & local API response caches\n+.env\n+.env.*\n+!.env.example\n+.doorprofit-cache/\n+\n # JavaScript / TypeScript\n node_modules/\n .next/\n dist/\n",
"firstChangedLine": 7
}
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-9236a10e596f34a2",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Modifieddoorprofit-example/README.md+3−0View changes
/sandbox/repo/doorprofit-example/README.md
Replacement excerpt
+- Docs disagree on the offender `radius` cap: the REST docs say max **5**+ miles, the MCP tool description says capped at **3**. Stay at ≤3 to be safe+ on both paths.- Offender data is sensitive PII.
Source: edit · chatcmpl-tool-ab033710910c6ab3
Recorded tool call
{
"type": "tool_call",
"seq": 168,
"ts": "2026-10-18T02:44:25.934Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/doorprofit-example/README.md",
"edits": [
{
"newText": "- Docs disagree on the offender `radius` cap: the REST docs say max **5**\n miles, the MCP tool description says capped at **3**. Stay at ≤3 to be safe\n on both paths.\n- Offender data is sensitive PII.",
"oldText": "- Offender data is sensitive PII."
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-ab033710910c6ab3",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 169,
"ts": "2026-10-18T02:44:25.934Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/doorprofit-example/README.md.",
"type": "text"
}
],
"details": {
"diff": " ...\n 118 granularity.\n 119 - `incidents` are **counts within 1,000 ft**, over a `date_from`→`date_to`\n 120 window that varies by city. There is no list of individual incidents, despite\n 121 what some marketing copy implies.\n+122 - Docs disagree on the offender `radius` cap: the REST docs say max **5**\n+123 miles, the MCP tool description says capped at **3**. Stay at ≤3 to be safe\n+124 on both paths.\n 122 - Offender data is sensitive PII. Handle it accordingly and check the\n 123 [terms](https://api.doorprofit.com/terms/) for permitted use.",
"patch": "--- /sandbox/repo/doorprofit-example/README.md\n+++ /sandbox/repo/doorprofit-example/README.md\n@@ -118,6 +118,9 @@\n granularity.\n - `incidents` are **counts within 1,000 ft**, over a `date_from`→`date_to`\n window that varies by city. There is no list of individual incidents, despite\n what some marketing copy implies.\n+- Docs disagree on the offender `radius` cap: the REST docs say max **5**\n+ miles, the MCP tool description says capped at **3**. Stay at ≤3 to be safe\n+ on both paths.\n - Offender data is sensitive PII. Handle it accordingly and check the\n [terms](https://api.doorprofit.com/terms/) for permitted use.\n",
"firstChangedLine": 122
}
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-ab033710910c6ab3",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
I need to investigate this further. Let me check the details.
I'll do a final check: run git status, list the files, and verify that the cache directory hasn't been created (it shouldn't exist since I haven't made any successful API calls). Also, I'll clean up pycache.
Let's verify.
cd /sandbox/repo && rm -rf doorprofit-example/__pycache__ && ls -la doorprofit-example && echo "=== git status ===" && git status --short && echo "=== ignore check ===" && mkdir -p doorprofit-example/.doorprofit-cache && touch doorprofit-example/.doorprofit-cache/x.json doorprofit-example/.env && git status --short --ignored | grep -E 'doorprofit-cache|\.env' ; rm -rf doorprofit-example/.doorprofit-cache doorprofit-example/.env
total 68
total 68 drwx------ 2 root root 4096 Oct 18 02:44 . drwxr-xr-x 4 root root 4096 Oct 18 02:42 .. -rw------- 1 root root 156 Oct 18 02:44 .env.example -rw------- 1 root root 6483 Oct 18 02:44 README.md -rw------- 1 root root 1960 Oct 18 02:43 crime-map-widget.html -rw------- 1 root root 8954 Oct 18 02:42 doorprofit.py -rw------- 1 root root 414 Oct 18 02:43 mcp-config.json -rw------- 1 root root 5719 Oct 18 02:43 mcp_probe.py -rw------- 1 root root 5680 Oct 18 02:42 report.mjs -rw------- 1 root root 10244 Oct 18 02:42 report.py === git status === M .gitignore ?? doorprofit-example/ === ignore check === !! doorprofit-example/.doorprofit-cache/ !! doorprofit-example/.env
Recorded tool call
{
"type": "tool_call",
"seq": 173,
"ts": "2026-10-18T02:44:30.262Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && rm -rf doorprofit-example/__pycache__ && ls -la doorprofit-example && echo \"=== git status ===\" && git status --short && echo \"=== ignore check ===\" && mkdir -p doorprofit-example/.doorprofit-cache && touch doorprofit-example/.doorprofit-cache/x.json doorprofit-example/.env && git status --short --ignored | grep -E 'doorprofit-cache|\\.env' ; rm -rf doorprofit-example/.doorprofit-cache doorprofit-example/.env"
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a38bb6808b58461b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 174,
"ts": "2026-10-18T02:44:30.262Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 68\ndrwx------ 2 root root 4096 Oct 18 02:44 .\ndrwxr-xr-x 4 root root 4096 Oct 18 02:42 ..\n-rw------- 1 root root 156 Oct 18 02:44 .env.example\n-rw------- 1 root root 6483 Oct 18 02:44 README.md\n-rw------- 1 root root 1960 Oct 18 02:43 crime-map-widget.html\n-rw------- 1 root root 8954 Oct 18 02:42 doorprofit.py\n-rw------- 1 root root 414 Oct 18 02:43 mcp-config.json\n-rw------- 1 root root 5719 Oct 18 02:43 mcp_probe.py\n-rw------- 1 root root 5680 Oct 18 02:42 report.mjs\n-rw------- 1 root root 10244 Oct 18 02:42 report.py\n=== git status ===\n M .gitignore\n?? doorprofit-example/\n=== ignore check ===\n!! doorprofit-example/.doorprofit-cache/\n!! doorprofit-example/.env\n",
"type": "text"
}
]
},
"run_id": "cmvd7vs6s005q0irivso5ehap",
"tool_id": "chatcmpl-tool-a38bb6808b58461b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything is clean. .env.example is tracked (not ignored) as intended.
Now, let me write the final answer. I need to cover the following:
- What I built (with paths)
- The pricing structure
- Whether this product is easy for me to manage
- That I got blocked → clearly: I can't get an API key myself (email verification required), so live calls are blocked. But since a developer workflow exists, I shouldn't stop — I built everything I could.
Actually — an important nuance regarding the instructions: "If this product doesn't have a developer workflow you can work on, state so clearly and stop." DoorProfit does have a developer workflow I can work on (hosted REST + hosted MCP, and I actually worked on them — initialize/tools/list succeeded). So I shouldn't stop. But I hit a partial blocker: I can't create an account myself (email verification). I have to state that clearly.
Also, I'll note that I didn't start a local stack or run any long-running commands — I complied.
Let me write it concisely and completely.
Done. Built and verified what I could without a key.
What I found
DoorProfit is a real hosted product — a US location-intelligence REST API (crime grades + incident counts, neighborhood demographics/income/housing/rent/weather/disaster risk, registered-offender search), keyed by address or lat/lng. It does have a developer workflow I can act on, so I proceeded:
https://api.doorprofit.com/v1/{crime,neighborhood,offenders,usage}— live, I got real 401s back with structured error bodieshttps://api.doorprofit.com/mcp— a hosted MCP server. I completedinitializeandtools/listagainst it successfully (both unauthenticated), confirming 4 tools- No local stack, nothing long-running. Zero install steps.
There is no SDK. The homepage's import DoorProfit from 'doorprofit' is decorative — no doorprofit package exists on npm or PyPI (I checked both registries directly; the PyPI 200 was a JS-challenge page). Integration is plain HTTPS GETs.
Files — /sandbox/repo/doorprofit-example/
| File | Notes |
|---|---|
doorprofit.py | Stdlib-only client: 4 endpoints, disk cache, 429 backoff, typed errors |
report.py | Due-diligence CLI; --dry-run (0 credits), --usage-only (free), --json |
report.mjs | Node 18+ equivalent, fetch, no packages |
mcp_probe.py | Hits the hosted MCP server; --list needs no key |
mcp-config.json | Drop-in config for Claude/ChatGPT/Cursor |
crime-map-widget.html | Embed widget (separate, cheaper quota) |
README.md | Pricing table, gotchas, budgeting notes |
Verified: both languages compile, dry-run prints correct curl + credit cost, no-key paths exit 2 with a signup hint, a bad key returns the live API's INVALID_API_KEY parsed correctly, mcp_probe.py --list works, and report rendering was tested against the documented payloads (including the incidents: null and offender-failure variants). Added .env / .doorprofit-cache/ to the root .gitignore.
How pricing works
Per-plan monthly call quota, not per-call. Live page is api.doorprofit.com/billing/ — the docs' "pricing page" links point at /pricing, which 404s.
| Plan | Price | Calls/mo | Daily | Rate | Embed views/mo | API overage |
|---|---|---|---|---|---|---|
| Free | $0 | 10 | 50 | 2/s | 5,000 | none — hard 429 |
| Starter | $25 | 300 | 1,500 | 5/s | 50,000 | $0.15/call |
| Growth | $50 | 600 | 3,000 | 10/s | 200,000 | $0.10/call |
| Pro | $100 | 1,200 | 6,000 | 20/s | 500,000 | $0.08/call |
| Enterprise | custom | custom | custom | custom | custom | — |
Embed overage is $0.50/1K on all paid tiers; overage bills at a $25 threshold or period end.
The non-obvious parts:
- Effective price per call is ~$0.083 on every paid tier ($25/300 = $50/600 = $100/1200). Upgrading buys rate limit and daily burst headroom, not cheaper data. Choose by req/sec.
- One report = 3 credits — no combined endpoint. Free plan ⇒ 3 reports/month. That's why the client caches.
- Offender pagination costs a credit per page.
/v1/usageis free and uncounted; every response also carriesX-Monthly-Remaining/X-Planheaders, so you can self-monitor at no cost.- Embed views bill per unique browser per month, exclude bots, and tiles + cached geocodes are free — displaying data publicly is far cheaper through the widget than via API calls. Attribution link is mandatory or the map self-disables.
500 Main St, Dallas, TX 75202is a documented free test address that never consumes quota.
Is it easy for me to manage?
Yes — unusually so. Stateless GETs, one auth param, clean JSON, structured error codes with documentation_url, a free uncounted usage endpoint, quota headers on every response, and a hosted MCP server I reached with zero setup. It's about as agent-friendly as an API gets. Two frictions: the small free quota means I must budget calls deliberately (hence the caching and --dry-run), and there's a real docs bug trail — broken /pricing links, a phantom npm SDK, and a radius cap the REST docs say is 5 miles while the MCP tool says 3.
Blocked
I could not obtain an API key, so no live data calls were made. Signup at api.doorprofit.com/signup/ is a two-step form (email → name/password) that requires email verification — there's a /api/resend_verification endpoint, confirming it. I have no mailbox, and I won't register an account against a fabricated address. Every endpoint rejects unauthenticated requests with 401, including the free test address, so that escape hatch doesn't work either.
Hand me a key (export DOORPROFIT_API_KEY=...) and python3 report.py --usage-only will confirm it for free, then a full report runs for 3 credits. Until then, --dry-run and mcp_probe.py --list both work with no key.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents