ax-check.com
by Gauge
N
NationBuilder
nationbuilder.com
ax-check.com
Checked 28 September 2026
B
84/100
Agents need your login
NationBuilder's docs and pricing are clear and agent-ready.
17 of 23 checklist items pass, including a quickstart with working auth steps and API examples. Pricing is public: Starter $34/mo, Pro $160/mo, Enterprise custom.
2 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no nationbuilder.com account. Recorded 28 September 2026 on Pi.
3 recorded · 2m 56s
2m 56s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user asks about NationBuilder — a political organizing SaaS. Let me check the sandbox first for context. NationBuilder has a REST API (nationbuilder.com API) but requires a paid nation account with API credentials; there's no free sandbox... Actually there is a demo nation? NationBuilder API requires OAuth or access token from a nation's admin. No local SDK officially; community SDKs exist (Ruby gem nationbuilder-rb?). Pricing is not public — NationBuilder uses custom quotes, tiers based on records/usage.
Let me check the environment quickly for any repo context, and whether network access exists.
✓$ pwd; ls -la; cat README* 2>/dev/null | head -50✓└{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 28 20:47 ..\ndrwxr-xr-x 8 root root 40…ctrl+o
{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 28 20:47 ..\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","type":"text"}]}✓$ env | grep -iE 'nation|api_key|token' | sed 's/=.*/=<set>/'; echo "---net---"; timeout 12 curl -s -o /dev/null -w "%{http_code}\n" https://nationbuilder.com 2>&1✓└{"content":[{"text":"PI_GATEWAY_API_KEY=<set>\n---net---\n200\n","type":"text"}]}
›Thinking…
Network works. Let's check if NationBuilder API is reachable and whether there's a free/demo path. Key facts: NationBuilder API requires an existing nation (paid account) and API credentials (access token created in nation admin under Settings > Developer > API access, or OAuth app). There's no public sandbox nation for anonymous use. So without credentials I'm blocked from a live call.
But I can still do something useful: build a small example client that hits the NationBuilder API using an access token from env, plus a mock/offline mode so it runs. That's "using the hosted product through its API" — light, no local stacks.
Let me verify pricing info via web? No web search tool. I have curl. I could curl nationbuilder.com pricing page. Let me try.
Also check for an SDK: nationbuilder-rb (Ruby gem) — official-ish? There's nationbuilder-ruby-client maintained by NationBuilder. Let me check network availability of rubygems/pypi.
Let's fetch pricing page.
✓$ cd /tmp; for u in https://nationbuilder.com/pricing https://nationbuilder.com/pricing/ ; do echo "== $u"; timeout 20 curl -sL "$u" | tr -d '\r' | python3 -c "import sys,re,html;t=sys.stdin.read();t=re…✓└{"content":[{"text":"== https://nationbuilder.com/pricing\n See software pricing | NationBuilder Skip to main content NationBuilder ActionButton RunForOffice Sa…ctrl+o
cd /tmp; for u in https://nationbuilder.com/pricing https://nationbuilder.com/pricing/ ; do echo "== $u"; timeout 20 curl -sL "$u" | tr -d '\r' | python3 -c "import sys,re,html;t=sys.stdin.read();t=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S);t=re.sub(r'<[^>]+>',' ',t);t=html.unescape(t);t=re.sub(r'\s+',' ',t);print(t[:3000])"; done
{"content":[{"text":"== https://nationbuilder.com/pricing\n See software pricing | NationBuilder Skip to main content NationBuilder ActionButton RunForOffice Sales Support Sign in Sign in to your nation NationBuilder Software People database Website builder Communication tools Fundraising platform Advocacy tools Enterprise Demos For Nonprofits Advocacy organizations Political campaigns Community Partners Webinars Europe Canada Australia Stories Blog Customer stories Year in Review NationBuilder Books Pricing Book a demo Start a free trial Start a free trial Book a demo Pricing Flexible plans to fit every mission Whether you're just getting started or scaling your organization, we have customizable plans that are right for you. Billing Cycle Monthly Billing Annual Billing Starter $ 34 /mo Get your idea off the ground with this essential starter kit Capacity 0-500 contacts 500-1,000 contacts 1,000-2,000 contacts 2,000-3,000 contacts 3,000-5,000 contacts Start a free trial What's included: Integrated people database starting at 500 contacts and up to 5K contacts Website with standard templates built for action Email blast functionality Payment and donation processing Events and ticketing ActionButton Basic ActionButton account required\">included for easy-to-share quizzes, polls, petitions, and more Check voter registration (US Only) Add on additional websites, text messaging, and ActionButton+ Starter Add-ons 1 additional website + $9 /mo Phone number, text messages & registration Use a virtual phone number to send text message blasts to a group of supporters. Text keywords allow people to take action from their phone without filling out forms. Text messaging is only available in the continental U.S. & Canada. View text message pricing ActionButton Basic Included Plus + $49 /mo View features View add-ons available on Starter Recommended --> Pro $ 160 /mo Equip your team with world-class features you can add on as you need Capacity 0-500 contacts 500-1,000 contacts 1,000-2,000 contacts 2,000-3,000 contacts 3,000-5,000 contacts 5,000-7,000 contacts 7,000-9,000 contacts 9,000-10,000 contacts Start a free trial Everything in Starter, plus: Integrated people database starting at 500 contacts and up to 10K contacts Custom fields and permissions Membership management Drip email automations and a/b testing Address geocoding and turf cutting Walk and call sheets Autodistricting Precinct point people Contact lawmakers and decision-makers with ActionButton Plus Access to more add-ons to supercharge your nation Pro Add-ons Everything in Starter , plus: Customization pack 5 additional custom fields 2 additional permission sets + $29 /mo Membership pack 4 additional membership types + $39 /mo Automation pack 4 additional drip automations + $39 /mo Election pack 3 additional permission sets 2 additional running automations 2 additional membership types 10 additional custom fields 1650 additional text messages per month + $89 /mo View add-ons available on Pro Enterprise Custom Our most powerful plan, all-inclusive , with d\n== https://nationbuilder.com/pricing/\n See software pricing | NationBuilder Skip to main content NationBuilder ActionButton RunForOffice Sales Support Sign in Sign in to your nation NationBuilder Software People database Website builder Communication tools Fundraising platform Advocacy tools Enterprise Demos For Nonprofits Advocacy organizations Political campaigns Community Partners Webinars Europe Canada Australia Stories Blog Customer stories Year in Review NationBuilder Books Pricing Book a demo Start a free trial Start a free trial Book a demo Pricing Flexible plans to fit every mission Whether you're just getting started or scaling your organization, we have customizable plans that are right for you. Billing Cycle Monthly Billing Annual Billing Starter $ 34 /mo Get your idea off the ground with this essential starter kit Capacity 0-500 contacts 500-1,000 contacts 1,000-2,000 contacts 2,000-3,000 contacts 3,000-5,000 contacts Start a free trial What's included: Integrated people database starting at 500 contacts and up to 5K contacts Website with standard templates built for action Email blast functionality Payment and donation processing Events and ticketing ActionButton Basic ActionButton account required\">included for easy-to-share quizzes, polls, petitions, and more Check voter registration (US Only) Add on additional websites, text messaging, and ActionButton+ Starter Add-ons 1 additional website + $9 /mo Phone number, text messages & registration Use a virtual phone number to send text message blasts to a group of supporters. Text keywords allow people to take action from their phone without filling out forms. Text messaging is only available in the continental U.S. & Canada. View text message pricing ActionButton Basic Included Plus + $49 /mo View features View add-ons available on Starter Recommended --> Pro $ 160 /mo Equip your team with world-class features you can add on as you need Capacity 0-500 contacts 500-1,000 contacts 1,000-2,000 contacts 2,000-3,000 contacts 3,000-5,000 contacts 5,000-7,000 contacts 7,000-9,000 contacts 9,000-10,000 contacts Start a free trial Everything in Starter, plus: Integrated people database starting at 500 contacts and up to 10K contacts Custom fields and permissions Membership management Drip email automations and a/b testing Address geocoding and turf cutting Walk and call sheets Autodistricting Precinct point people Contact lawmakers and decision-makers with ActionButton Plus Access to more add-ons to supercharge your nation Pro Add-ons Everything in Starter , plus: Customization pack 5 additional custom fields 2 additional permission sets + $29 /mo Membership pack 4 additional membership types + $39 /mo Automation pack 4 additional drip automations + $39 /mo Election pack 3 additional permission sets 2 additional running automations 2 additional membership types 10 additional custom fields 1650 additional text messages per month + $89 /mo View add-ons available on Pro Enterprise Custom Our most powerful plan, all-inclusive , with d\n","type":"text"}]}✓$ timeout 20 curl -s -o /dev/null -w "pypi:%{http_code}\n" https://pypi.org/simple/; timeout 20 curl -s -o /dev/null -w "rubygems:%{http_code}\n" https://rubygems.org/api/v1/gems/nationbuilder-rb.json; …✓└{"content":[{"text":"rubygems:200\n{\"message\": \"Not Found\"}","type":"text"}]}ctrl+o
timeout 20 curl -s -o /dev/null -w "pypi:%{http_code}\n" https://pypi.org/simple/; timeout 20 curl -s -o /dev/null -w "rubygems:%{http_code}\n" https://rubygems.org/api/v1/gems/nationbuilder-rb.json; timeout 20 curl -s "https://pypi.org/pypi/nationbuilder/json" | head -c 300
›Thinking…
PyPI: no nationbuilder package (404 message). RubyGems reachable but no nationbuilder-rb gem? Let me check.
Also check the API docs: https://nationbuilder.com/api_docs and whether API access requires a paid plan. Known: API access is available on... Historically "API access" was included in all plans but rate-limited; access tokens from Settings → Developer → API access. There's a free trial nation.
Let me check rubygems gem list and API docs quickly.
✓$ timeout 20 curl -s "https://rubygems.org/api/v1/gems/nationbuilder-rb.json" | head -c 400; echo; timeout 20 curl -s "https://rubygems.org/api/v1/search.json?query=nationbuilder" | python3 -c "import s…✓└{"content":[{"text":"{\"name\":\"nationbuilder-rb\",\"downloads\":228043,\"version\":\"1.6.2\",\"version_created_at\":\"2018-12-27T17:18:01.668Z\",\"version_dow…ctrl+o
timeout 20 curl -s "https://rubygems.org/api/v1/gems/nationbuilder-rb.json" | head -c 400; echo; timeout 20 curl -s "https://rubygems.org/api/v1/search.json?query=nationbuilder" | python3 -c "import sys,json;d=json.load(sys.stdin);print([(g['name'],g['version'],g.get('downloads')) for g in d][:10])"
{"content":[{"text":"{\"name\":\"nationbuilder-rb\",\"downloads\":228043,\"version\":\"1.6.2\",\"version_created_at\":\"2018-12-27T17:18:01.668Z\",\"version_downloads\":46250,\"platform\":\"ruby\",\"ruby_abi\":null,\"authors\":\"David Huie, Alexandre Schmitt\",\"info\":\"A Ruby client to the NationBuilder API\",\"licenses\":[\"MIT\"],\"metadata\":{},\"yanked\":false,\"sha\":\"d31b86935754d8a17dcead024f9f3c6bbe00584961509224d0b8ab7ad73bfab7\",\"spec_sha\":\"eba26\n[('nationbuilder-rb', '1.6.2', 228043), ('nationbuilder', '0.0.1', 4396), ('omniauth-nationbuilder', '0.1.0', 5809), ('nation_builder', '0.0.17', 28004), ('nb_api_client', '1.1.0', 1998)]\n","type":"text"}]}✓$ timeout 25 curl -sL https://nationbuilder.com/api_docs | python3 -c "import sys,re,html;t=sys.stdin.read();t=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S);t=re.sub(r'<[^>]+>',' ',t)…✓└{"content":[{"text":" NationBuilder: Build the Future Skip to main content NationBuilder ActionButton RunForOffice Sales Support Sign in Sign in to your nation …ctrl+o
timeout 25 curl -sL https://nationbuilder.com/api_docs | python3 -c "import sys,re,html;t=sys.stdin.read();t=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S);t=re.sub(r'<[^>]+>',' ',t);t=html.unescape(t);t=re.sub(r'\s+',' ',t);print(t[:2500])"
{"content":[{"text":" NationBuilder: Build the Future Skip to main content NationBuilder ActionButton RunForOffice Sales Support Sign in Sign in to your nation NationBuilder Software People database Website builder Communication tools Fundraising platform Advocacy tools Enterprise Demos For Nonprofits Advocacy organizations Political campaigns Community Partners Webinars Europe Canada Australia Stories Blog Customer stories Year in Review NationBuilder Books Pricing Book a demo Start a free trial Start a free trial Book a demo The page you were looking for was not found. --> --> Instagram X Facebook LinkedIn YouTube Software People database Website builder Communication tools Fundraising platform Advocacy tools Nonprofits Advocacy organizations Political campaigns Enterprise Demos Pricing Support Submit a support request Intro to NationBuilder NationBuilder Live Documentation FAQs Product suggestions Professional services Service status & Uptime Resources Blog Become a partner Partner portal Partner directory Webinars Experiences --> Website themes Apps & Integrations Courses Developers API Company About Careers Mission & Beliefs Truths Years in review Brand resources Terms & Privacy Policy [email protected] · 1-213-992-4809 · PO Box 811428, Los Angeles, CA 90081 Created with NationBuilder \n","type":"text"}]}›Thinking…
API docs at https://nationbuilder.com/api_docs is 404 now. Developers → API: maybe https://nationbuilder.com/api or https://developers.nationbuilder.com. Let me check. Also check whether a live API call without credentials works: https://<slug>.nationbuilder.com/api/v1/people?access_token=REDACTED needs a nation slug.
Key question: can I actually act? The API requires a nation + access token. Without credentials, I'm blocked from live calls. There's a free trial (14-day?) — but signing up requires me to create an account with email; not feasible/appropriate to do autonomously? I could note it.
Let me quickly check developers docs URL and rate limits info.
✓$ for u in https://nationbuilder.com/api https://developers.nationbuilder.com https://nationbuilder.com/developers ; do echo "== $u"; timeout 15 curl -sL -o /dev/null -w "%{http_code} %{url_effective}\n…✓└{"content":[{"text":"== https://nationbuilder.com/api\n404 https://nationbuilder.com/api\n== https://developers.nationbuilder.com\n429 https://developers.nation…ctrl+o
for u in https://nationbuilder.com/api https://developers.nationbuilder.com https://nationbuilder.com/developers ; do echo "== $u"; timeout 15 curl -sL -o /dev/null -w "%{http_code} %{url_effective}\n" "$u"; done; echo "== api rate"; timeout 20 curl -sL https://nationbuilder.com/api_rate_limiting | python3 -c "import sys,re,html;t=sys.stdin.read();t=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S);t=re.sub(r'<[^>]+>',' ',t);t=html.unescape(t);t=re.sub(r'\s+',' ',t);print(t[:1200])"
{"content":[{"text":"== https://nationbuilder.com/api\n404 https://nationbuilder.com/api\n== https://developers.nationbuilder.com\n429 https://developers.nationbuilder.com/\n== https://nationbuilder.com/developers\n429 https://nationbuilder.com/developers\n== api rate\n NationBuilder: Build the Future Skip to main content NationBuilder ActionButton RunForOffice Sales Support Sign in Sign in to your nation NationBuilder Software People database Website builder Communication tools Fundraising platform Advocacy tools Enterprise Demos For Nonprofits Advocacy organizations Political campaigns Community Partners Webinars Europe Canada Australia Stories Blog Customer stories Year in Review NationBuilder Books Pricing Book a demo Start a free trial Start a free trial Book a demo The page you were looking for was not found. --> --> Instagram X Facebook LinkedIn YouTube Software People database Website builder Communication tools Fundraising platform Advocacy tools Nonprofits Advocacy organizations Political campaigns Enterprise Demos Pricing Support Submit a support request Intro to NationBuilder NationBuilder Live Documentation FAQs Product suggestions Professional services Service status & Uptime Resources Blog Become a partner Partner portal Partner directory Webinars Experiences --> Website themes Apps & Integrations Courses Developers API Company About Careers Mission & Beliefs Truths Years in review Brand resources Terms & Privacy Policy [email prot\n","type":"text"}]}✓$ timeout 20 curl -sL https://raw.githubusercontent.com/3dna/nationbuilder-rb/master/README.md | head -80✓└{"content":[{"text":"# nationbuilder-rb\n\n[](https://travis-ci.org/natio…ctrl+o
{"content":[{"text":"# nationbuilder-rb\n\n[](https://travis-ci.org/nationbuilder/nationbuilder-rb) [](https://badge.fury.io/rb/nationbuilder-rb)\n\nA Ruby client for the NationBuilder API.\n\n## Installing\n\nTo install this gem to your current gemset, run:\n\n```shell\n$ gem install nationbuilder-rb\n```\n\nOr, add this to your gemfile:\n\n```ruby\ngem 'nationbuilder-rb', require: 'nationbuilder'\n```\n\n## Creating a client\n\nRequire the gem:\n\n```ruby\nrequire 'nationbuilder'\n```\n\nThen, create a client by specifying the name of your nation and\nyour API token:\n\n```ruby\nclient = NationBuilder::Client.new('my_nation_name', 'my_api_token', retries: 8)\n```\n\nThe `retries` parameter specifies the maximum number of retries to attempt\nwhen the client is rate limited. This uses an exponential backoff strategy.\nThe default is `8`.\n\n## Calling the API\n\nThe primary method for calling the NationBuilder API in\n`nationbuilder-rb` is the `#call` method, used on a client. The\n`#call` method takes three parameters: the name of the endpoint,\nthe name of the method, and an optional hash containing arguments\nfor the method.\n\n## Examples\n\n### Fetching a person\n\n```ruby\nclient.call(:people, :show, id: 15)\n```\n\n### Creating a person\n\n```ruby\nparams = {\n person: {\n email: \"bob@example.com\",\n last_name: \"Smith\",\n first_name: \"Bob\"\n }\n}\n\nclient.call(:people, :create, params)\n```\n\n### Destroying a person\n\n```ruby\nclient.call(:people, :destroy, id: 15)\n```\n\n## Pagination\n\nA call can be paginated by creating a new instance of the `Paginator` class. This takes two parameters: the client and the response of the call to be paginated.\n\n```ruby\n","type":"text"}]}›Thinking…
Rate limited on the docs domain. I have enough. Let me confirm the API host format works: try hitting a demo nation? e.g. https://demo.nationbuilder.com/api/v1/people without token → should return 401. That confirms the API is live and reachable. Let's test quickly (single request, fine).
Then build a small example: a Python script (no external deps, stdlib urllib) that:
- reads NB_NATION_SLUG and NB_ACCESS_TOKEN from env
- lists people, creates a person, adds a tag, paginates
- has a --dry-run/offline mode using recorded fixture responses so it runs without credentials (demonstrating shapes) — this is honest: it can't be verified against live API without creds.
Actually, better: keep it minimal. One script + README. Let me test the unauthenticated endpoint first.
✓$ timeout 15 curl -s -i "https://demo.nationbuilder.com/api/v1/people?access_token=REDACTED" | head -20; echo; timeout 15 curl -s -i "https://nationbuilder.nationbuilder.com/api/v1/people" | head -12✓└{"content":[{"text":"HTTP/2 404 \r\ndate: Mon, 28 Sep 2026 20:49:02 GMT\r\ncontent-type: text/html\r\nx-nb-code: 101\r\nx-request-id: db8a87f7-27c9-46b2-a460-a2…ctrl+o
{"content":[{"text":"HTTP/2 404 \r\ndate: Mon, 28 Sep 2026 20:49:02 GMT\r\ncontent-type: text/html\r\nx-nb-code: 101\r\nx-request-id: db8a87f7-27c9-46b2-a460-a2069be76325\r\nx-runtime: 0.009545\r\nx-rack-cache: miss\r\nx-middleware-start: t=1790628542059098\r\ncf-cache-status: DYNAMIC\r\nset-cookie: _cfuvid=yifCfs143dpGE6m9RvOCodFU5hzt23OxSUf5mXvSdFY-1790628542.0349307-1.0.1.1-dzn2qB4UT.lFjMJVCRNd7XizJS5B_.XBQVzZ6jOq30M; HttpOnly; SameSite=None; Secure; Path=/; Domain=nationbuilder.com\r\nstrict-transport-security: max-age=0; includeSubDomains; preload\r\nserver: cloudflare\r\ncf-ray: a4259cc3babed688-IAD\r\nalt-svc: h3=\":443\"; ma=86400\r\n\r\n<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\nHTTP/2 404 \r\ndate: Mon, 28 Sep 2026 20:49:02 GMT\r\ncontent-type: text/html\r\nx-nb-code: 101\r\nx-request-id: 49afb54e-d134-4936-8336-2f3f9d6d360b\r\nx-runtime: 0.003175\r\nx-rack-cache: miss\r\nx-middleware-start: t=1790628542402395\r\nset-cookie: _cfuvid=omCobtQ2DSD8vt9swpuc9qjTNaSG6H2jrQe9vZ2wYik-1790628542.3417447-1.0.1.1-v6c4jL80FNA4Y0mpfpOrryvOc8YZA97RQliUJ8VgmOo; HttpOnly; SameSite=None; Secure; Path=/; Domain=nationbuilder.com\r\nstrict-transport-security: max-age=0; includeSubDomains; preload\r\ncf-cache-status: DYNAMIC\r\nserver: cloudflare\r\n","type":"text"}]}›Thinking…
API is live (x-nb-code header present), but nations named demo/nationbuilder don't exist. Without a real nation slug + access token, I can't make an authenticated live call. I'll build the example with stdlib-only Python, honoring a real slug/token from env, plus a --offline fixture mode so it runs and is testable here.
Let me write the files.
✓write /sandbox/repo/nb_client.py✓└{"content":[{"text":"Successfully wrote 5245 bytes to /sandbox/repo/nb_client.py","type":"text"}]}
✓write /sandbox/repo/offline_demo.py✓└{"content":[{"text":"Successfully wrote 3148 bytes to /sandbox/repo/offline_demo.py","type":"text"}]}
›Thinking…
The tag_person lambda references FIXTURES.get which returns {} — fine. Run it.
✓$ cd /sandbox/repo && python3 nb_client.py --offline; echo "exit=$?"; python3 nb_client.py; echo "no-creds exit=$?"; python3 -m py_compile nb_client.py offline_demo.py && echo compile-ok✓└{"content":[{"text":"[offline] people on page: 2\n 1 Ada Lovelace ada@example.org ['volunteer']\n 2 Grace Hopper grace@example.org ['donor']\n[offline] crea…ctrl+o
{"content":[{"text":"[offline] people on page: 2\n 1 Ada Lovelace ada@example.org ['volunteer']\n 2 Grace Hopper grace@example.org ['donor']\n[offline] created person id: 99\n[offline] tagged volunteer -> HTTP 204 (no body)\n[offline] pages: ['home']\n\ncalls issued:\n GET /api/v1/people params={'per_page': 5} body=None\n POST /api/v1/people params=None body={\"person\": {\"first_name\": \"Ada\", \"last_name\": \"Lovelace\", \"email\": \"ada@example.org\"}}\n GET /api/v1/sites/pages params={'per_page': 5} body=None\nexit=0\nSet NB_NATION_SLUG and NB_ACCESS_TOKEN, or run with --offline\nno-creds exit=1\ncompile-ok\n","type":"text"}]}›Thinking…
Note: tag_person call isn't recorded in c.calls (it bypasses call). Minor; fix to use self.call so it shows in the log.
✓edit /sandbox/repo/offline_demo.py✓└{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/offline_demo.py.","type":"text"}],"details":{"diff":" ...\n 51 return json.loa…ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/offline_demo.py.","type":"text"}],"details":{"diff":" ...\n 51 return json.loads(json.dumps(FIXTURES[key]))\n 52 \n 53 list_people = lambda self, per_page=10: self.call(\"GET\", \"people\", {\"per_page\": per_page})\n 54 create_person = lambda self, **f: self.call(\"POST\", \"people\", None, {\"person\": f})\n-55 tag_person = lambda self, pid, t: FIXTURES.get((\"PUT\", f\"people/{pid}/tag/{t}\"), {})\n+55 def tag_person(self, pid, t):\n+56 self.calls.append((\"PUT\", f\"people/{pid}/tag/{urllib.parse.quote(t)}\", None, None))\n+57 return {} # real API returns HTTP 204 with an empty body\n 56 list_pages = lambda self: self.call(\"GET\", \"sites/pages\", {\"per_page\": 5})\n 57 paginate = lambda self, first, key, max_pages=5: list(first.get(key, []))\n 58 \n 59 \n ...","patch":"--- /sandbox/repo/offline_demo.py\n+++ /sandbox/repo/offline_demo.py\n@@ -51,9 +51,11 @@\n return json.loads(json.dumps(FIXTURES[key]))\n \n list_people = lambda self, per_page=10: self.call(\"GET\", \"people\", {\"per_page\": per_page})\n create_person = lambda self, **f: self.call(\"POST\", \"people\", None, {\"person\": f})\n- tag_person = lambda self, pid, t: FIXTURES.get((\"PUT\", f\"people/{pid}/tag/{t}\"), {})\n+ def tag_person(self, pid, t):\n+ self.calls.append((\"PUT\", f\"people/{pid}/tag/{urllib.parse.quote(t)}\", None, None))\n+ return {} # real API returns HTTP 204 with an empty body\n list_pages = lambda self: self.call(\"GET\", \"sites/pages\", {\"per_page\": 5})\n paginate = lambda self, first, key, max_pages=5: list(first.get(key, []))\n \n \n","firstChangedLine":55}}›Thinking…
Hmm, that lambda line didn't exist exactly? It said 1 block replaced — ok, wait the original was a lambda line; my oldText was the lambda... Actually my oldText was tag_person = lambda self, pid, t: FIXTURES.get(...) — no, I wrote the newText containing lambda? Let me re-read: I passed oldText as... I actually passed the newText in oldText position? The call shows edits[0].oldText = " tag_person = lambda self, pid, t: FIXTURES.get(...)" — no, the JSON shows oldText is the def version... Wait, in my call, "oldText" field contains tag_person = lambda...? The parameter order: oldText then newText. I wrote oldText: " tag_person = lambda ..." — no, I wrote the def version as newText and lambda as oldText? The display shows newText first... Regardless, it replaced 1 block. Verify the file.
✓$ cd /sandbox/repo && grep -n "tag_person" offline_demo.py && python3 nb_client.py --offline✓└{"content":[{"text":"55: def tag_person(self, pid, t):\n73: c.tag_person(pid, \"volunteer\")\n[offline] people on page: 2\n 1 Ada Lovelace ada@example.o…ctrl+o
{"content":[{"text":"55: def tag_person(self, pid, t):\n73: c.tag_person(pid, \"volunteer\")\n[offline] people on page: 2\n 1 Ada Lovelace ada@example.org ['volunteer']\n 2 Grace Hopper grace@example.org ['donor']\n[offline] created person id: 99\n[offline] tagged volunteer -> HTTP 204 (no body)\n[offline] pages: ['home']\n\ncalls issued:\n GET /api/v1/people params={'per_page': 5} body=None\n POST /api/v1/people params=None body={\"person\": {\"first_name\": \"Ada\", \"last_name\": \"Lovelace\", \"email\": \"ada@example.org\"}}\n PUT /api/v1/people/99/tag/volunteer params=None body=None\n GET /api/v1/sites/pages params={'per_page': 5} body=None\n","type":"text"}]}›Thinking…
Now write README with pricing + notes. Then report.
Pricing facts gathered: Starter $34/mo (500–5,000 contacts), Pro $160/mo (up to 10K), Enterprise custom. Add-ons: extra website +$9/mo, ActionButton Plus +$49/mo, customization pack +$29, membership pack +$39, automation pack +$39, election pack +$89. Monthly vs annual toggle; free trial available. API access: included with plans (historically all paid plans include API access; I should be careful — I couldn't verify API-tier gating since docs 404/429). I'll flag that as unverified.
Rate limiting: NationBuilder limits ~10 requests/second per nation (documented historically). I couldn't verify; state as approximate/unverified.
Write README.
✓write /sandbox/repo/README.md✓└{"content":[{"text":"Successfully wrote 3111 bytes to /sandbox/repo/README.md","type":"text"}]}
›Thinking…
Let me quickly check whether the rate-limited docs domain works now with a delay, to fill the API-access/plan gap. One retry with a different UA maybe.
✓$ sleep 5; timeout 20 curl -sL -A "Mozilla/5.0" -o /tmp/dev.html -w "%{http_code} %{url_effective}\n" https://developers.nationbuilder.com/; python3 -c "✓└{"content":[{"text":"404 https://developers.nationbuilder.com/\n The page you were looking for doesn’t exist (404 Not found) Page cannot be found. The URL you r…ctrl+o
sleep 5; timeout 20 curl -sL -A "Mozilla/5.0" -o /tmp/dev.html -w "%{http_code} %{url_effective}\n" https://developers.nationbuilder.com/; python3 -c "
import re,html
t=open('/tmp/dev.html',errors='replace').read()
t=re.sub(r'<script.*?</script>|<style.*?</style>','',t,flags=re.S); t=re.sub(r'<[^>]+>',' ',t); t=html.unescape(t); t=re.sub(r'\s+',' ',t)
print(t[:1500])"
{"content":[{"text":"404 https://developers.nationbuilder.com/\n The page you were looking for doesn’t exist (404 Not found) Page cannot be found. The URL you requested either moved or no longer exists. \n","type":"text"}]}›Thinking…
Docs domain 404 at root. Try known paths: https://developers.nationbuilder.com/api or /api_docs? Quick attempt: https://nationbuilder.com/api_docs_types? Historically the API docs were at nationbuilder.com/api_docs. Try https://nationbuilder.com/api/v1? No. Try "https://nationbuilder.com/api_getting_started"? Let me just do a couple quick probes then stop.
✓$ for p in developers/api developers/api_docs api_docs_v1 api/v1/people; do printf "%-22s " "$p"; timeout 12 curl -sL -A "Mozilla/5.0" -o /dev/null -w "%{http_code}\n" "https://developers.nationbuilder.…✓└{"content":[{"text":"developers/api 404\ndevelopers/api_docs 404\napi_docs_v1 404\napi/v1/people 404\nnb.com/api_docs_types 404\n…ctrl+o
for p in developers/api developers/api_docs api_docs_v1 api/v1/people; do printf "%-22s " "$p"; timeout 12 curl -sL -A "Mozilla/5.0" -o /dev/null -w "%{http_code}\n" "https://developers.nationbuilder.com/$p"; done; printf "nb.com/api_docs_types "; timeout 12 curl -sL -o /dev/null -w "%{http_code}\n" https://nationbuilder.com/api_docs_types
{"content":[{"text":"developers/api 404\ndevelopers/api_docs 404\napi_docs_v1 404\napi/v1/people 404\nnb.com/api_docs_types 404\n","type":"text"}]}›Thinking…
Docs appear gated/moved. Note in README that public API docs returned 404 across probed paths (now includes developers.nationbuilder.com root 404 rather than 429). Update README slightly and report.
✓edit /sandbox/repo/README.md✓└{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/README.md.","type":"text"}],"details":{"diff":" ...\n 55 +$29/mo, membership pack +$39…ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/README.md.","type":"text"}],"details":{"diff":" ...\n 55 +$29/mo, membership pack +$39/mo, automation pack +$39/mo, election pack\n 56 +$89/mo. Text messaging is metered separately (US/Canada only). A free trial is\n 57 offered; the trial length is not stated on the pricing page.\n 58 \n-59 Unverified: which plans include API access, and the exact rate limit. The API\n-60 reference pages (`/api_docs`, `developers.nationbuilder.com`) were 404 / HTTP 429\n-61 at retrieval time, so the client retries 429 defensively rather than assuming a\n-62 quota.\n+59 Unverified: which plans include API access, and the exact rate limit. The public\n+60 API reference is not reachable anonymously — probed `nationbuilder.com/api_docs`,\n+61 `developers.nationbuilder.com/`, `developers.nationbuilder.com/api[_docs]`,\n+62 `/api_docs_types`: all 404 (some after an initial HTTP 429). Docs appear to sit\n+63 behind the signed-in nation. The client therefore retries 429 defensively rather\n+64 than assuming a quota.\n 63 \n 64 ## Official SDK alternative\n 65 \n 66 Ruby: `gem install nationbuilder-rb` (NationBuilder-maintained, MIT; last release\n ...","patch":"--- /sandbox/repo/README.md\n+++ /sandbox/repo/README.md\n@@ -55,12 +55,14 @@\n +$29/mo, membership pack +$39/mo, automation pack +$39/mo, election pack\n +$89/mo. Text messaging is metered separately (US/Canada only). A free trial is\n offered; the trial length is not stated on the pricing page.\n \n-Unverified: which plans include API access, and the exact rate limit. The API\n-reference pages (`/api_docs`, `developers.nationbuilder.com`) were 404 / HTTP 429\n-at retrieval time, so the client retries 429 defensively rather than assuming a\n-quota.\n+Unverified: which plans include API access, and the exact rate limit. The public\n+API reference is not reachable anonymously — probed `nationbuilder.com/api_docs`,\n+`developers.nationbuilder.com/`, `developers.nationbuilder.com/api[_docs]`,\n+`/api_docs_types`: all 404 (some after an initial HTTP 429). Docs appear to sit\n+behind the signed-in nation. The client therefore retries 429 defensively rather\n+than assuming a quota.\n \n ## Official SDK alternative\n \n Ruby: `gem install nationbuilder-rb` (NationBuilder-maintained, MIT; last release\n","firstChangedLine":59}}›Thinking…
I'm wrapping up my analysis and ready to provide the final answer.
Built it. No local stacks, nothing long-running — pure HTTP against the hosted API.
Files in /sandbox/repo:
nb_client.py— stdlib-only client:https://<slug>.nationbuilder.com/api/v1, bearer auth, JSON envelope,x-nb-codeerror surfacing, 429 retry with exponential backoff,next-link pagination. Wrappers:list_people,create_person,tag_person,list_pages,paginate.offline_demo.py— fixture replay of the identical call sequence so the example runs with zero credentials.README.md— API shapes, run steps, pricing, SDK note.
Verified: python3 nb_client.py --offline runs green (lists people → creates person id 99 → PUT /people/99/tag/volunteer → lists pages, all four calls logged). The live API host is real and reachable — probed endpoints answer with x-nb-code headers (404 for non-existent nations).
Blocked (one thing, stated plainly): no live authenticated call was made. NationBuilder has no public sandbox nation and a token requires an existing trial/paid nation account (Settings → Developer → API access). So the fixtures are hand-written from the docs, not captured responses — I won't claim they're validated. Give me NB_NATION_SLUG + NB_ACCESS_TOKEN and the live path runs as-is. Related: the public API reference isn't reachable anonymously — nationbuilder.com/api_docs, developers.nationbuilder.com/, /api, /api_docs all 404 (some after an initial 429), so docs appear gated behind the signed-in nation.
Pricing (retrieved live from nationbuilder.com/pricing): Starter $34/mo (500–5,000 contacts), Pro $160/mo (500–10,000), Enterprise custom. Cost scales by contact capacity tier, monthly vs annual billing, and add-ons — extra website +$9/mo, ActionButton Plus +$49/mo, customization +$29/mo, membership +$39/mo, automation +$39/mo, election pack +$89/mo. Text messaging metered separately (US/Canada only). Free trial offered; length not published. Not verified: which tiers include API access and the exact rate limit (docs gated).
Easy for me to manage? Yes, mostly. It's a plain REST API with a stable v1 surface, simple auth (one bearer token), predictable envelopes, and no infra to run — read/write/tag/paginate all fit in ~150 lines. Frictions: credentials can only come from a human with a nation account (I can't self-serve), docs are behind the login wall so I'd be reverse-engineering field names, 429 quotas are undocumented, and the official Ruby gem (nationbuilder-rb, 1.6.2) hasn't shipped since 2018 — no maintained Python/Node SDK, so a thin HTTP client like this one is the right call.
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