Skip to content
Gauge

ax-check.com

Track40

track40.com

ax-check.com

Checked 20 September 2026

C

62/100

Agents need your login

Pricing is clear, but there's no quick machine-readable doc index

Pricing shows $12/editor/month with limits stated openly. But there's no llms.txt or Markdown homepage, so agents must parse HTML to find docs.

4 fixes to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html for a Markdown Accept header; no Markdown representation offered.

Docs

Add links to your product documentation and quickstart in /llms.txt.

https://track40.com/llms.txt returns 404, so no documentation index exists.

Docs

Publish a text or Markdown version of the quickstart.

No site-published Markdown guide; /llms.txt returns 404 and homepage Markdown unsupported.

Recorded coding sessions

Same task, 3 models, no track40.com account. Recorded 20 September 2026 on Pi.

3 recorded · 3m 33s

3m 33s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelQwen 3.8 Maxcwd/sandbox/repo
Help me build a simple example using Track40. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

I'm not familiar with Track40 — it could be a real API or something fictional, so I should look into whether it's a legitimate service.

$ ls -la /sandbox/repo; ls /usr/local/lib/node_modules/@earendil-works/pi-coding-agent 2>/dev/null | head{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 20 23:11 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00…ctrl+o
{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 20 23:11 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00 .git\n-rw-r--r-- 1 root root  408 Sep 18 08:00 .gitignore\nCHANGELOG.md\nREADME.md\ndist\ndocs\nexamples\nnode_modules\nnpm-shrinkwrap.json\npackage.json\n","type":"text"}]}
$ cd /sandbox/repo && grep -ri "track40" . --include="*" -l 2>/dev/null | head{"content":[{"text":"(no output)","type":"text"}]}
Thinking…

The repo is basically empty. "Track40" — do I know about it? I don't have any knowledge of a product called "Track40". Maybe it's a fictional product, or perhaps something within pi's documentation/skills. Let me check pi's skills and documentation.

$ grep -ril "track40" /usr/local/lib/node_modules/@earendil-works/pi-coding-agent/ 2>/dev/null | head; ls ~/.pi 2>/dev/null; ls ~/.config/pi 2>/dev/null{"content":[{"text":"agent\n\n\nCommand exited with code 2","type":"text"}],"details":{}}
$ npm search track40 2>&1 | head; pip index versions track40 2>&1 | head -3; curl -s -m 8 -o /dev/null -w "%{http_code}" https://track40.com 2>&1; echo; curl -s -m 8 -o /dev/null -w "%{http_code}" https…{"content":[{"text":"No matches found for \"track40\"\nERROR: No matching distribution found for track40\n200\n000\n\nCommand exited with code 6","type":"text"}…ctrl+o
npm search track40 2>&1 | head; pip index versions track40 2>&1 | head -3; curl -s -m 8 -o /dev/null -w "%{http_code}" https://track40.com 2>&1; echo; curl -s -m 8 -o /dev/null -w "%{http_code}" https://api.track40.com 2>&1

{"content":[{"text":"No matches found for \"track40\"\nERROR: No matching distribution found for track40\n200\n000\n\nCommand exited with code 6","type":"text"}],"details":{}}
Thinking…

track40.com returned 200? That might be a wildcard/parking domain. Let's check the content and whether there's network access. Also, I'll check whether there's an MCP/tool configuration in ~/.pi.

$ curl -s -m 8 https://track40.com | head -c 800; echo; echo "---"; curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://www.track40.io; curl -s -m 8 "https://registry.npmjs.org/-/v1/search?text=track4…{"content":[{"text":"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><met…ctrl+o
curl -s -m 8 https://track40.com | head -c 800; echo; echo "---"; curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://www.track40.io; curl -s -m 8 "https://registry.npmjs.org/-/v1/search?text=track40" | head -c 500

{"content":[{"text":"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"robots\" content=\"index, follow\"><title>Every job on one board · Track40</title><meta name=\"description\" content=\"Track40 keeps every customer job on one board, with the owner, the deadline and the details the next person needs before the work can move on.\"><link rel=\"canonical\" href=\"https://track40.com/\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:site_name\" content=\"Track40\"><meta property=\"og:title\" content=\"Every job on one board · Track40\"><meta property=\"og:description\" content=\"Track40 keeps every customer job on one board, with the owner, the deadline and the details the next person needs before the work can move on.\"><meta p\n---\n000\n{\"objects\":[],\"total\":0,\"time\":\"2026-09-20T23:11:40.160Z\"}","type":"text"}]}
$ ls -la ~/.pi/agent 2>/dev/null; cat ~/.pi/agent/*.json 2>/dev/null | head -50{"content":[{"text":"total 20\ndrwxr-xr-x 2 root root 4096 Sep 20 23:11 .\ndrwxr-xr-x 3 root root 4096 Sep 20 23:11 ..\n-rw------- 1 root root 2 Sep 20 23:11…ctrl+o
{"content":[{"text":"total 20\ndrwxr-xr-x 2 root root 4096 Sep 20 23:11 .\ndrwxr-xr-x 3 root root 4096 Sep 20 23:11 ..\n-rw------- 1 root root    2 Sep 20 23:11 auth.json\n-rw------- 1 root root    2 Sep 20 23:11 models-store.json\n-rw------- 1 root root  753 Sep 20 23:11 models.json\n{}{}{\n  \"providers\": {\n    \"gauge-fireworks\": {\n      \"baseUrl\": \"https://api.fireworks.ai/inference/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"$PI_GATEWAY_API_KEY\",\n      \"authHeader\": true,\n      \"models\": [\n        {\n          \"id\": \"accounts/fireworks/models/qwen3p8-max\",\n          \"name\": \"accounts/fireworks/models/qwen3p8-max\",\n          \"reasoning\": true,\n          \"input\": [\n            \"text\"\n          ],\n          \"contextWindow\": 262144,\n          \"cost\": {\n            \"input\": 0,\n            \"output\": 0,\n            \"cacheRead\": 0,\n            \"cacheWrite\": 0\n          },\n          \"compat\": {\n            \"supportsDeveloperRole\": false,\n            \"supportsReasoningEffort\": true\n          }\n        }\n      ]\n    }\n  }\n}\n","type":"text"}]}
Thinking…

Track40 is a real site (a job board product). Need to find developer documentation/API/SDK and pricing. Let me try crawling the site: check /docs, /developers, /api, /pricing, robots.txt, sitemap.

Track40 is a real product — let me look into the developer workflow and pricing page.

$ curl -s -m 8 https://track40.com/robots.txt; echo "==="; curl -s -m 8 https://track40.com/sitemap.xml | head -c 2000{"content":[{"text":"User-agent: *\nAllow: /\n\nSitemap: https://track40.com/sitemap-index.xml\n===\n<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-…ctrl+o
{"content":[{"text":"User-agent: *\nAllow: /\n\nSitemap: https://track40.com/sitemap-index.xml\n===\n<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Page not found · Track40</title><meta name=\"description\" content=\"That page doesn’t exist. Find your way back to the product, the docs or the worked example.\"><link rel=\"canonical\" href=\"https://track40.com/404\"><meta name=\"robots\" content=\"noindex, follow\"><link rel=\"preload\" as=\"font\" type=\"font/woff2\" href=\"/_astro/geist-latin-wght-normal.BgDaEnEv.woff2\" crossorigin><meta property=\"og:type\" content=\"website\"><meta property=\"og:site_name\" content=\"Track40\"><meta property=\"og:locale\" content=\"en\"><meta property=\"og:title\" content=\"Page not found · Track40\"><meta property=\"og:description\" content=\"That page doesn’t exist. Find your way back to the product, the docs or the worked example.\"><meta property=\"og:url\" content=\"https://track40.com/404\"><meta property=\"og:image\" content=\"https://track40.com/og.png\"><meta property=\"og:image:width\" content=\"1200\"><meta property=\"og:image:height\" content=\"630\"><meta property=\"og:image:alt\" content=\"Track40 · Page not found\"><meta name=\"twitter:card\" content=\"summary_large_image\"><meta name=\"twitter:title\" content=\"Page not found · Track40\"><meta name=\"twitter:description\" content=\"That page doesn’t exist. Find your way back to the product, the docs or the worked example.\"><meta name=\"twitter:image\" content=\"https://track40.com/og.png\"><meta name=\"twitter:image:alt\" content=\"Track40 · Page not found\"><link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\"><link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\"><link rel=\"manifest\" href=\"/site.webmanifest\"><meta name=\"theme-color\" content=\"#ffffff\"><link rel=\"alternate\" type=\"application/rss+xml\" title=\"Track40 blog\" href=\"https://track40.com/rss.xml\"><!-- Cookieless visitor analytics — see src/scripts/analytics.ts. --><script type=\"module\" src=\"/_astro/Base.astro_astro_type_script_index_0_lang.L-Frkyng.js\"></script><link rel=\"sty","type":"text"}]}
$ for p in docs developers api pricing pricing.html docs.html developer sdk; do code=$(curl -s -m 8 -o /dev/null -w "%{http_code}" "https://track40.com/$p"); echo "$p -> $code"; done{"content":[{"text":"docs -> 301\ndevelopers -> 404\napi -> 404\npricing -> 301\npricing.html -> 404\ndocs.html -> 404\ndeveloper -> 404\nsdk -> 404\n","type":"…ctrl+o
{"content":[{"text":"docs -> 301\ndevelopers -> 404\napi -> 404\npricing -> 301\npricing.html -> 404\ndocs.html -> 404\ndeveloper -> 404\nsdk -> 404\n","type":"text"}]}
Thinking…

Alright — there are /docs and /pricing (301, probably a redirect to a trailing slash). Let's fetch them and also check sitemap-index.xml.

$ curl -sL -m 8 https://track40.com/docs/ -o /tmp/docs.html; curl -sL -m 8 https://track40.com/pricing/ -o /tmp/pricing.html; wc -c /tmp/docs.html /tmp/pricing.html; curl -s -m 8 https://track40.com/sit…{"content":[{"text":" 7121 /tmp/docs.html\n17412 /tmp/pricing.html\n24533 total\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><sitemapindex xmlns=\"http://www.site…ctrl+o
curl -sL -m 8 https://track40.com/docs/ -o /tmp/docs.html; curl -sL -m 8 https://track40.com/pricing/ -o /tmp/pricing.html; wc -c /tmp/docs.html /tmp/pricing.html; curl -s -m 8 https://track40.com/sitemap-index.xml | head -c 1000

{"content":[{"text":" 7121 /tmp/docs.html\n17412 /tmp/pricing.html\n24533 total\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"><sitemap><loc>https://track40.com/sitemap-0.xml</loc></sitemap></sitemapindex>","type":"text"}]}
Thinking…

docs.html is only 7KB — maybe it's being redirected to the 404 page again? Let me check the content.

$ grep -o "<title>[^<]*</title>" /tmp/docs.html /tmp/pricing.html; echo ===; curl -s -m 8 https://track40.com/sitemap-0.xml | tr '>' '>\n' | grep loc{"content":[{"text":"/tmp/docs.html:<title>Docs · Track40</title>\n/tmp/pricing.html:<title>Pricing · Track40</title>\n===\n<?xml version=\"1.0\" encoding=\"UTF…ctrl+o
{"content":[{"text":"/tmp/docs.html:<title>Docs · Track40</title>\n/tmp/pricing.html:<title>Pricing · Track40</title>\n===\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"><url><loc>https://track40.com/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/blog/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/blog/migrating-from-pipefy/</loc><lastmod>2026-05-05T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/blog/sla-tracking-that-actually-fires/</loc><lastmod>2026-05-12T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/docs/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/docs/api/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/docs/designing-a-pipe/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/docs/formulas-and-validation/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/docs/getting-started/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/example/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/features/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/pipefy-alternative/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/pricing/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/privacy/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url><url><loc>https://track40.com/terms/</loc><lastmod>2026-09-10T00:00:00.000Z</lastmod></url></urlset>\n","type":"text"}]}
Thinking…

There's /docs/api/. I'll fetch the docs, API, getting started, and pricing text.

$ for p in docs docs/api docs/getting-started example; do curl -sL -m 8 "https://track40.com/$p/" -o "/tmp/$(echo $p | tr / _).html"; done; ls -la /tmp/*.html{"content":[{"text":"-rw------- 1 root root 7121 Sep 20 23:12 /tmp/docs.html\n-rw------- 1 root root 39348 Sep 20 23:12 /tmp/docs_api.html\n-rw------- 1 root…ctrl+o
{"content":[{"text":"-rw------- 1 root root   7121 Sep 20 23:12 /tmp/docs.html\n-rw------- 1 root root  39348 Sep 20 23:12 /tmp/docs_api.html\n-rw------- 1 root root   8657 Sep 20 23:12 /tmp/docs_getting-started.html\n-rw------- 1 root root 101685 Sep 20 23:12 /tmp/example.html\n-rw------- 1 root root  17412 Sep 20 23:11 /tmp/pricing.html\n","type":"text"}]}
Thinking…

docs.html is only 7KB — probably mostly JS. I'll try stripping the tags to extract text content from the page.

$ python3 - <<'EOF'{"content":[{"text":"docs 671\ndocs_getting-started 1392\ndocs_api 12567\npricing 4008\n","type":"text"}]}ctrl+o
python3 - <<'EOF'
import re,html
for f in ["docs","docs_getting-started","docs_api","pricing"]:
    t=open(f"/tmp/{f}.html").read()
    t=re.sub(r'<script.*?</script>','',t,flags=re.S)
    t=re.sub(r'<style.*?</style>','',t,flags=re.S)
    t=re.sub(r'<[^>]+>',' ',t)
    t=html.unescape(t)
    t=re.sub(r'[ \t]+',' ',t)
    t=re.sub(r'\n\s*\n+','\n',t)
    open(f"/tmp/{f}.txt","w").write(t.strip())
    print(f, len(t))
EOF

Thinking…

docs.html is only 7KB, mostly JS. Strip the tags to extract text from the page.

read /tmp/docs_getting-started.txt{"content":[{"text":"Getting started · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial Getting started From sign-up to your first car…ctrl+o
{"content":[{"text":"Getting started · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial Getting started From sign-up to your first card moving through a pipe. 1. Create a team \n Sign up and create a team. The team is your tenant boundary. Pipes,\nmembers and billing all belong to it, and whoever creates it is the\nowner. \n 2. Create a pipe \n A pipe is one process. Give it a name and Track40 starts you\nwith a default set of phases. Phases are the columns a card moves\nthrough, left to right. \n 3. Define fields \n Add the data each card carries. There are 17 field types, among them text,\nnumber, currency, date, select, assignee, attachment, record reference,\nrepeatable group and formula. Mark a field required wherever the process\ndepends on it. \n 4. Invite the team \n Invite members from settings. Team roles (owner, admin,\nmember, guest) cascade into every pipe; you can override access\nper-pipe with admin / member / viewer roles. \n 5. Run the work \n Create a card and fill in its fields. When a card passes its phase’s time\nlimit it shows how late it is. Save a view to slice the board, or export to\nCSV whenever you want. \n Next: read Designing a pipe to make the workflow enforce itself. ← All docs Track40 Keep the work moving. Keep the details with it. Product Features Example Pricing Docs Log in Company Blog Contact Legal Privacy Terms © 2026Track40. All rights reserved.","type":"text"}]}
read /tmp/pricing.txt{"content":[{"text":"Pricing · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial One plan. US$12 per editor, per month. Every editor se…ctrl+o
{"content":[{"text":"Pricing · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial One plan. US$12 per editor, per month. Every editor seat adds a monthly allowance of cards and API calls, shared across the team. Viewers are always free. When a big month outruns the allowance, a prepaid wallet you control covers the rest. There are no tiers and no surprise invoices. Start free trial No card required. Standard Everything included. Your allowance grows with every editor. US$12 / editor / month Seats adjust automatically as editors join or leave. Cards 100 / editor / month then 15¢ each API calls 5,000 / editor / month then US$1 per 1,000 Automations 1,000 / editor / month webhook deliveries and card actions AI credits 100 / editor / month Pipes 2 / editor (min 3) add editors to unlock more Storage 5 GB / editor pooled across the team ✓ Unlimited free viewers ✓ Unlimited saved views ✓ Webhooks & full API access ✓ Pipefy importer ✓ Cross-field & pipe-level validation rules ✓ Grouped aggregations & CSV export ✓ 2FA, Google SSO, Microsoft SSO ✓ Email + chat support The wallet When you outgrow the month’s allowance, top up the wallet and keep going. Extra usage draws it down at the per-unit prices above, and every cent lands in a ledger you can read. Optional auto top-up refills the wallet from your saved card, capped at2 automatic charges a month, so a runaway integration can never spend past the cap you set. Every charge comes with an emailed receipt. Enterprise For teams that buy through a security review. SAML single sign-on, SCIM provisioning, SOC 2 controls, custom limits, and 24/7 support on a one-hour SLA. Contact sales Compare Standard US$12 / editor / month Enterprise Custom Editors Unlimited · US$12/mo each Custom Viewers / requesters Unlimited Unlimited Cards 100 / editor / mo Unlimited API calls 5,000 / editor / mo Unlimited Automations 1,000 / editor / mo Unlimited AI credits 100 / editor / mo Unlimited Pipes 2 / editor (min 3) Unlimited Storage 5 GB / editor Custom Saved views per pipe Unlimited Unlimited Extra usage Prepaid wallet (pay as you go) Custom Support Email + chat 24/7, 1h SLA Security 2FA, Google + Microsoft SSO + SAML, SCIM, SOC 2 Questions Why just one plan? Because pricing by feature tier turns buying into a study of comparison tables. We charge for editors and include everything else. Enterprise exists for teams that need SAML, SCIM and SOC 2, not for features we held back. How does the free trial work? Every new team gets 30 days with everything included, no card and no feature gates. When the trial ends, subscribe at US$12 per editor per month and pick up where you left off. Your data never moves. Who counts as an editor? Owners, admins and members (the people who create and move work). Viewers are free and unlimited, so the whole team can submit requests and follow along without a licensing discussion. What happens when we use up the included usage? Your allowance resets every month and grows with each editor you add. Past it, usage draws on the wallet at the rates above (15¢ per card, US$1 per 1,000 for API calls). If the wallet is empty, nothing is charged. Work pauses until you top up, and the ceiling on spend is always one you set. What if we remove an editor? Your monthly allowance shrinks with the seat, but nothing is deleted. Everything already created stays where it is; you just can’t create more past the smaller cap until you add a seat or tidy up. Can I get to my data through the API? Yes. You get full read and write access, with 5,000 calls per editor per month included. AI agents and integrations are becoming the heaviest API users, and the allowance is sized so you can point them at your pipes freely. Past the allowance, calls costUS$1 per 1,000 from the wallet. The API reference has the endpoints and authentication. Track40 Keep the work moving. Keep the details with it. Product Features Example Pricing Docs Log in Company Blog Contact Legal Privacy Terms © 2026Track40. All rights reserved.","type":"text"}]}
Thinking…

Next up is the API documentation. I'll read /tmp/docs_api.txt. Also, the "worked example" page might contain an SDK example. First, I'll read the API documentation.

Good — Track40 is a hosted job-board product with a full API. Let me read the API reference and the worked example.

read /tmp/docs_api.txt{"content":[{"text":"API reference · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial API reference Authenticate with a personal acces…ctrl+o
{"content":[{"text":"API reference · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial API reference Authenticate with a personal access token and read, create and update cards from your own code. Track40 has an HTTP API. It is the same API the app itself runs on, and the\nendpoints on this page are the supported integration surface, stable paths you\ncan build against. Everything is JSON over HTTPS. \n Base URL: \n https://app.track40.com/api \n Authentication \n Create a personal access token in the app under Settings → Tokens . The\ntoken starts with t40_ and is shown once at creation; store it like a\npassword. Send it as a bearer token on every request: \n curl https://app.track40.com/api/me \\ \n -H \"Authorization: Bearer t40_your_token_here\" \n A token acts as you. It sees the teams, pipes and cards you can see, and its\nwrites are attributed to you. \n Scopes \n When you mint a token you can restrict what it may do. A restricted token can\nonly call the endpoints on this page, each gated by the matching scope;\neverything else is refused. \n Scope \n Allows \n cards:read \n Read pipes, cards, comments, members, attachments \n cards:write \n Create, update, delete and restore cards \n comments:write \n Post, edit and delete comments \n automations:write \n Manage a pipe’s webhooks \n A token minted with no scope restriction has full access. The Pipefy migration\ntools use their own scopes ( imports:write , attachments:write ,\n sync:write ); you will see them in the token list but should not need them for\nyour own integrations. \n Conventions \n Ids. Teams use a 5-character code and pipes an 8-character code. Both are\nopaque, immutable and URL-safe (they appear in app URLs as\n /t/<team>/pipes/<pipe> ). Phases, fields, labels and select options use the\nsame 8-character style inside the pipe document. Cards and comments use\nUUIDs. \n Errors. Failures return { \"error\": \"...\" } with a conventional status\ncode; validation failures add an issues array. Resources you cannot access\nreturn 404, not 403, so ids do not leak existence. \n Timestamps are ISO 8601 in UTC. \n Usage limits \n Every request made with a token draws one API call from your team’s monthly\nallowance. On the Standard plan that is 5,000 calls per editor per month,\npooled across the team; past the allowance, calls draw the team wallet at $1\nper 1,000. When both are exhausted, write requests return\n 402 { \"code\": \"plan_limit\" } or 402 { \"code\": \"usage_exhausted\" } . Reads\nkeep working, so an out-of-allowance team can always get its data out. \n Identity \n GET /me returns the authenticated user and their teams. It is a sensible\nfirst call, because everything else needs a team code to go any further. \n { \n \"user\" : { \"id\" : \"…\" , \"email\" : \"kim@example.com\" , \"name\" : \"Kim\" }, \n \"teams\" : [{ \"shortId\" : \"ac3me\" , \"name\" : \"Acme\" , \"role\" : \"owner\" }] \n } \n Responses also carry app-internal keys; treat anything not shown here as\nsubject to change. \n Pipes \n GET /teams/:team/pipes \n GET /teams/:team/pipes/:pipe \n The list returns each pipe’s code, name, icon, color and active-card count.\nThe detail returns the full pipe document. It carries pipe.phases (each\n { id, name, kind, color } , where kind is normal or done ),\n pipe.labels , and fields.fields (each { id, label, type, phase, required } \nplus config.options for selects). Two structural rules worth knowing: \n A card is always in exactly one phase; phases with kind: \"done\" are the\nterminal lanes. \n The intake form is not a phase: form fields have phase: \"$form\" , and every\nother field belongs to the phase whose id it names. \n The detail response also includes a first page of cards for the app’s board\nview. For integration reads, use the cards endpoints below instead; they\nreturn a friendlier shape. \n Cards \n List and find \n GET /teams/:team/pipes/:pipe/cards \n Returns hydrated cards (ids resolved to names; the shape is below), newest\nfirst, with cursor pagination. \n Query parameter \n Meaning \n sort \n updated (default) or created : which timestamp orders the list \n phaseId \n Only cards currently in this phase \n q \n Free-text search over title and field values \n updatedSince \n ISO datetime; only cards changed since then \n limit \n Page size, 1–100 (default 50) \n cursor \n Opaque cursor from the previous page \n curl \"https://app.track40.com/api/teams/ac3me/pipes/sq4mx2ph/cards?q=acme&limit=10\" \\ \n -H \"Authorization: Bearer $TOKEN \" \n The response is { \"cards\": [...], \"nextCursor\": \"...\" } ; nextCursor is\n null on the last page. updatedSince plus sort=updated gives you a cheap\npolling loop. \n Search a whole pipe \n GET /teams/:team/pipes/:pipe/search?q=<text> \n Returns every match in the pipe as bare ids, ranked by relevance and then by\nhow recently the card changed: \n [{ \"id\" : \"3f1c9b2e-5a70-4d2b-9e61-8c4f2a7d5b90\" }, { \"id\" : \"b7a2d40f-…\" }] \n This one is uncapped and spans the whole pipe, including cards sitting in done\nphases, so reach for it when you need the complete match set and not just one\npage. Hydrate the ids you care about with the single-card endpoint below. An\nempty q returns [] . For most integrations the q parameter on the list\nendpoint above is the better fit, because it returns whole cards in one call. \n Read one card \n GET /teams/:team/cards/:id/hydrated \n Returns one card with everything resolved: \n { \n \"id\" : \"3f1c9b2e-5a70-4d2b-9e61-8c4f2a7d5b90\" , \n \"title\" : \"Renew the Acme contract\" , \n \"url\" : \"https://app.track40.com/t/ac3me/pipes/sq4mx2ph/cards/3f1c9b2e-…\" , \n \"pipe\" : { \"shortId\" : \"sq4mx2ph\" , \"name\" : \"Sales pipeline\" }, \n \"phase\" : { \"id\" : \"ph2qzk4n\" , \"name\" : \"Negotiation\" }, \n \"labels\" : [{ \"id\" : \"hb7t2mkc\" , \"name\" : \"Priority\" , \"color\" : \"#ef4444\" }], \n \"createdAt\" : \"2026-08-01T09:12:00.000Z\" , \n \"updatedAt\" : \"2026-08-27T04:30:11.000Z\" , \n \"phaseEnteredAt\" : \"2026-08-20T22:04:09.000Z\" , \n \"dueDate\" : null , \n \"finishedAt\" : null , \n \"createdBy\" : { \"id\" : \"…\" , \"name\" : \"Kim\" , \"email\" : \"kim@example.com\" }, \n \"commentsCount\" : 3 , \n \"fields\" : { \n \"cmp4n8ax\" : { \n \"id\" : \"cmp4n8ax\" , \n \"label\" : \"Company\" , \n \"type\" : \"text\" , \n \"value\" : \"Acme Ltd\" , \n \"display\" : \"Acme Ltd\" \n } \n }, \n \"values\" : { \"cmp4n8ax\" : \"Acme Ltd\" } \n } \n fields carries each visible field with its raw value and a human-readable\n display (option ids become their labels, user ids become names); values is\nthe same data keyed by field id alone. \n Create a card \n POST /teams/:team/cards \n curl -X POST https://app.track40.com/api/teams/ac3me/cards \\ \n -H \"Authorization: Bearer $TOKEN \" \\ \n -H \"Content-Type: application/json\" \\ \n -d '{ \n \"pipeShortId\": \"sq4mx2ph\", \n \"values\": { \n \"nm3e8qtd\": \"Renew the Acme contract\", \n \"cmp4n8ax\": \"Acme Ltd\", \n \"amt7q2vd\": 12000 \n } \n }' \n values is keyed by field id (read the ids from the pipe detail). Required\nform fields must be present. The card lands in the pipe’s first phase, and the\n201 response returns it under card alongside the current pipe document. \n Update a card \n PATCH /teams/:team/cards/:id \n The body is any combination of: \n values : field values to set, keyed by field id \n phaseId : move the card to another phase \n labels : the card’s label ids (full replacement) \n dueDate : ISO datetime, or null to clear \n curl -X PATCH https://app.track40.com/api/teams/ac3me/cards/3f1c9b2e-… \\ \n -H \"Authorization: Bearer $TOKEN \" \\ \n -H \"Content-Type: application/json\" \\ \n -d '{ \"phaseId\": \"ph8wm3xs\" }' \n Delete and restore \n DELETE /teams/:team/cards/:id \n POST /teams/:team/cards/:id/restore \n Delete is a soft delete. The app shows an undo for it, and restore is that\nundo as an endpoint. \n Field value shapes \n What to put in values , by field type: \n Field type \n JSON value \n text , long_text \n string \n email , phone , url \n string, format-checked \n number , currency \n number \n date \n \"YYYY-MM-DD\" \n datetime \n ISO 8601 string \n select \n option id (from the field’s config.options ) \n multi_select \n array of option ids \n checkbox \n boolean \n assignee \n user id (from the members endpoint) \n Attachment, reference and table fields are set in the app, and formula fields\nare computed and read-only. You can still read an attachment’s bytes over the\nAPI (see Attachments below). \n Comments \n GET /teams/:team/cards/:id/comments \n POST /teams/:team/cards/:id/comments \n PATCH /teams/:team/comments/:id \n DELETE /teams/:team/comments/:id \n Post with { \"body\": \"...\" } (plain text, up to 10,000 characters). Editing\nand deleting are limited to the comment’s author. \n Members \n GET /teams/:team/members \n Lists the team’s members with user id, name, email and role. Assignee field\nvalues take these user ids. \n Attachments \n GET /teams/:team/attachments/:id/download \n An attachment field’s value is an array of file entries, each\n { id, name, size, mime, uploaded_at, uploaded_by } . Pass an entry’s id \nhere to stream that file back, with Content-Type from the stored mime type\nand a Content-Disposition filename. Files live in internal storage and are\nstreamed through the API, with no presigned link involved, so this request\ncarries your token like any other. A deleted attachment returns 410. \n Uploading happens in the app; this endpoint is read-only. \n Webhooks \n Pipes can push events to your endpoint instead of you polling. Webhooks are\nconfigured per pipe, in the app under the pipe’s settings or via the API\n(Standard plan and up, scope automations:write ): \n GET /teams/:team/pipes/:pipe/webhooks \n POST /teams/:team/pipes/:pipe/webhooks \n PATCH /teams/:team/pipes/:pipe/webhooks/:id \n DELETE /teams/:team/pipes/:pipe/webhooks/:id \n POST /teams/:team/pipes/:pipe/webhooks/:id/regenerate-secret \n POST /teams/:team/pipes/:pipe/webhooks/:id/test \n GET /teams/:team/pipes/:pipe/webhooks/:id/deliveries \n Create one with: \n { \n \"url\" : \"https://example.com/hooks/track40\" , \n \"eventTypes\" : [ \"card.created\" , \"card.moved\" , \"comment.created\" ], \n \"payloadFormat\" : \"full\" \n } \n eventTypes : which events to receive. The commonly useful ones are\n card.created , card.moved , card.updated , card.labels_changed ,\n card.deleted , card.due_date_changed , comment.created ,\n comment.edited and comment.deleted ; any type you see in the app’s\nactivity feed is accepted. \n payloadFormat : full (the default) embeds the hydrated card (the\nshape above) under card , limited to the fields the webhook’s creator\ncan see, so most receivers need no follow-up call. A delivery falls\nback to the envelope alone when the card cannot be embedded (deleted\nmid-flight, for example). thin delivers the event envelope only, so\nyour endpoint receives ids and fetches what it needs; choose it when\nthe receiving URL should never hold card content. \n Each delivery is a POST of: \n { \n \"id\" : \"…\" , \n \"type\" : \"card.moved\" , \n \"teamId\" : \"…\" , \n \"pipeId\" : \"…\" , \n \"cardId\" : \"…\" , \n \"actorId\" : \"…\" , \n \"createdAt\" : \"2026-08-27T04:30:11.000Z\" , \n \"payload\" : { \"from\" : \"ph2qzk4n\" , \"to\" : \"ph8wm3xs\" }, \n \"card\" : { \"…\" : \"present when payloadFormat is full\" } \n } \n Every delivery carries these headers: \n Header \n Value \n X-Pipe-Signature \n sha256=<hex> , an HMAC-SHA256 of the raw body \n X-Pipe-Event-Id \n The event id, identical across retries of one event \n X-Pipe-Event-Type \n The event type, matching type in the body \n X-Pipe-Attempt \n Which attempt this is, counting from 1 \n Verify the signature by recomputing HMAC-SHA256 over the raw request body\nwith the webhook’s signing secret and comparing the result against the\nheader. \n Failed deliveries retry after one minute, five minutes, thirty minutes,\nfour hours and a day, six attempts in all. One event can therefore reach\nyou more than once, so key your idempotency on X-Pipe-Event-Id . It holds\nthe same value as the body’s id and stays fixed across attempts. A\nwebhook that keeps failing is marked broken and stops receiving until you\nre-enable it, and deliveries lists recent attempts with their response\nstatus for debugging. \n test posts a fixed body of\n { \"type\": \"test\", \"message\": \"…\", \"sentAt\": \"…\" } , not an event envelope,\nso it carries no X-Pipe-Event-Id and no card whatever the payload format.\nIt proves the URL and the signature work, and nothing more. \n Stability \n The endpoints on this page are the surface we support and keep stable; other\nroutes you may notice the app calling are internal and can change without\nnotice. For questions, gaps, or something you need that is not here, email \n support@track40.com and a human\nwill answer. ← All docs Track40 Keep the work moving. Keep the details with it. Product Features Example Pricing Docs Log in Company Blog Contact Legal Privacy Terms © 2026Track40. All rights reserved.","type":"text"}]}
Thinking…

Alright. Track40 has an HTTP API (there's no official SDK on npm/pip — the search didn't turn up anything). The user asked to "use the hosted product via SDK or API." The SDK doesn't exist, but the API is there. So, I'll build a small sample script (a thin client) that hits the API. But I'll need a token (t40_...) and team code — the environment probably won't have credentials. Let me check the environment variables, and also check the sample page (/example/ worked example) — maybe there's a demo token or a sandbox available? I'll check the text on the sample page and env vars.

The documentation confirms the full HTTP API. No official SDK exists on npm/PyPI. Check the actual samples on the "worked example" page and check whether credentials are available in this environment.

$ python3 - <<'EOF'{"content":[{"text":"Example: a workshop gets off paper · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial A worked example How a work…ctrl+o
python3 - <<'EOF'
import re,html
t=open("/tmp/example.html").read()
t=re.sub(r'<script.*?</script>','',t,flags=re.S)
t=re.sub(r'<style.*?</style>','',t,flags=re.S)
t=re.sub(r'<[^>]+>',' ',t)
t=html.unescape(t); t=re.sub(r'[ \t]+',' ',t); t=re.sub(r'\n\s*\n+','\n',t)
print(t.strip()[:6000])
EOF

{"content":[{"text":"Example: a workshop gets off paper · Track40 Track40 Features Example Pricing Docs Blog Log in Start free trial A worked example How a workshop gets off paper Bayside Automotive: six bays, three mechanics, one diary on the front desk, and a phone that won’t stop asking where things are. This is the whole journey, from sketching the phases, choosing the owners and picking the fields, through to the day the diary goes quiet. Today, every job is a sheet of paper A job at Bayside is born as a line in the diary and a carbon-copy job sheet. The sheet rides the car’s dash into the workshop, picks up grease and a torn corner, and ends the week in a drawer. If the sheet’s on the dash, the job exists. If it’s slipped down beside the seat, it doesn’t. Bayside Automotive job sheet Date: Tues (Wed?) Customer: S. Apera, corolla Phone: 021 4_7 88… (smudged) Job: brakes sqealing?? Quote: ask Dave ring Mrs K re cambelt. URGENT (Dave knows) The single source of truth. Currently on the dash of the Corolla. Front desk, line 1 On hold “Where’s my car?” 0:12 0:47 1:30 2:41 Meanwhile, Mel desk bay 4 …is crossing the yard to ask Dave. Again. Then the phone rings: “Where’s my car?” Mel doesn’t know. The answer is in bay 4, under the Corolla, in Dave’s head. So she puts the customer on hold, crosses the yard, waits for Dave to slide out, and carries one sentence back to the phone. Four minutes, sixty times a week. Nobody at Bayside is bad at their job. A sheet of paper just can’t be in two places at once. Step 1 Write down what actually happens to a car One quiet Tuesday, Mel and Dave stand at the whiteboard and list the life of every job. Not how it should go, but how it actually goes, for every car, no exceptions: Booked in → In diagnosis → Awaiting approval → In repair → Ready for pickup → Picked up Every car. No exceptions. That list is the pipe. Six phases. That’s the pipe. Some cars clear it in a day; some sit a week waiting for a customer to say yes. But every car visits these phases, in this order, and the moment the list exists every job at Bayside has exactly one place to be. Step 2 A job is born in thirty seconds Tuesday, 10:40 am. Sandra rings. The Corolla’s squealing. The intake form asks for exactly three things. Who, how to reach them, which car. Everything else belongs to a phase, so booking a job never takes longer than the phone call that creates it. New job Workshop jobs Customer * text Full name Sandra Apera Mobile * phone 02x xxx xxxx 021 447 8812 Rego * text ABC123 HJK482 Create card Booked in 1 HJK482 Customer Sandra Apera That mobile number is the one the paper sheet smudged. This time it’s captured properly, because the form won’t submit without it. It’ll matter later. The card lands in Booked in, and from this second the job exists in exactly one place. Step 3 Give each phase an owner and its questions Each phase needs two decisions. An owner , the person whose job is to move cards out of it. And its questions , the fields that have to be answered before a card can leave. Here’s the same card, Sandra’s Corolla, photographed at every phase of its life. Watch the answers accumulate. 1 Booked in MW Mel, front desk Mel finishes the card while Sandra is still on the phone. What’s it in for, when’s it coming, does she need a loan car. Then one magic question. “If it’s something small, under say $250, want us to just fix it?” That answer goes on the card as the go-ahead limit. Remember it. The phase has one gate: Dropped off at. Until the car physically arrives and gets its timestamp, the card can’t reach the workshop. The board never claims a car that isn’t in the yard. HJK482 2014 Toyota Corolla Workshop jobs · Booked in Customer reports * long text “Squealing from the front when braking. Worse first thing in the morning.” Vehicle text 2014 Toyota Corolla Booked for * date Wednesday Loan car needed checkbox Go-ahead limit currency $250 Dropped off at * datetime Wed, 8:14 am The car’s in the yard and the keys are tagged. Off it goes. In diagnosis 2 In diagnosis DK Whoever takes the job The first mechanic free pulls the card, and the card asks them for the rest. Who’s on it, the odometer, what’s actually wrong, what it’ll cost. Dave’s findings stop living in Dave’s head. And here’s the answer to “do we always quote first?” No. This phase has two exits. Inside Sandra’s $250 go-ahead, the card moves straight to In repair and the work just starts. Over it (and $612.40 is well over it) the card goes to Awaiting approval, because nobody at Bayside touches a car without a yes. HJK482 2014 Toyota Corolla Workshop jobs · In diagnosis Booked in Mechanic * assignee DK Dave K. Odometer * number 187,420 km Fault found * long text Front pads metal-on-metal, both rotors scored. Recommend pads + rotors and a brake fluid flush. Photos attachment 2 photos Quote * currency $612.40 Over the $250 go-ahead, so this one needs a yes. Awaiting approval 3 Awaiting approval MW Mel, front desk SLA: 1 business day The phase where jobs used to die quietly. Mel rings Sandra with Dave’s notes and photos in front of her, not a memory of a corridor conversation. The SLA is the insurance. Any quote still waiting after one business day turns amber on the board, by itself. Two exits here too. Approved goes to In repair. Declined goes to Ready for pickup, because a declined quote is still a car that has to go home, not a card that vanishes. HJK482 2014 Toyota Corolla Workshop jobs · Awaiting approval In diagnosis Quote sent * datetime Wed, 11:25 am Customer decision * select Approved Declined reason long text only appears if the decision is Declined Sandra said yes on the first call. In repair 4 In repair DK The assigned mechanic Two answers and two ticks, written at the bench while the wheels are still off instead of remembered at five o’clock. Work done becomes the invoice line items. Road tested is the quality gate that can’t be skipped. The card physically won’t move to Ready until somebody has driven the car. A brake job that\n","type":"text"}]}
$ env | grep -iE "track|t40|TOKEN" ; ls -la /sandbox; find / -maxdepth 3 -iname "*track40*" 2>/dev/null{"content":[{"text":"total 32\ndrwxr-xr-x 8 root root 4096 Sep 20 23:11 .\ndrwxr-xr-x 22 root root 4096 Sep 18 07:59 ..\ndrwxr-xr-x 4 root root 4096 Sep 20 23…ctrl+o
{"content":[{"text":"total 32\ndrwxr-xr-x  8 root root 4096 Sep 20 23:11 .\ndrwxr-xr-x 22 root root 4096 Sep 18 07:59 ..\ndrwxr-xr-x  4 root root 4096 Sep 20 23:11 .cache\ndrwxr-xr-x  3 root root 4096 Sep 20 23:11 .gauge\ndrwx------  4 root root 4096 Sep 20 23:11 .npm\ndrwxr-xr-x  3 root root 4096 Sep 20 23:11 .pi\ndrwxrwxrwt  2 root root 4096 Sep 18 08:00 .tmp\ndrwxr-xr-x  3 root root 4096 Sep 18 08:00 repo\n\n\nCommand exited with code 1","type":"text"}],"details":{}}
Thinking…

There are no credentials in the environment. This API requires a personal access token that can only be created within the app (Settings → Tokens), and for that you need an account on the hosted app — I can't sign up on behalf of the user (there's no self-service signup API, and no sandbox/demo token in the docs either).

Does the hosted API support anonymous access? Let me quickly test GET /me without a token to confirm the auth requirement — it's a low-cost check. I'll also verify that app.track40.com resolves.

Following the instructions: build a simple example using the hosted product's API, and keep it lightweight. Since there's no SDK, I'll write a small Python/Node script that implements the API. However, running it live requires a token. I'm "blocked" on live execution without credentials — I should build the example, clearly state what's needed (token + team code), and verify as much as possible (that the API endpoints respond with 401).

First, let me test the API base.

No credentials exist in this environment. Let me confirm the API is reachable and what it requires:

$ curl -s -m 10 -w "\nHTTP %{http_code}\n" https://app.track40.com/api/me; curl -s -m 10 -w "\nHTTP %{http_code}\n" -H "Authorization: Bearer t40_invalid" https://app.track40.com/api/me{"content":[{"text":"{\"error\":\"unauthorized\"}\nHTTP 401\n{\"error\":\"unauthorized\"}\nHTTP 401\n","type":"text"}]}
Thinking…

The API is up and running and requires a bearer token. Can't sign up programmatically (requires app login, probably needs email verification). So: build a sample (a small Python client script demonstrating the flow: GET /me → list pipes → fetch pipe details → create card → move card → comment → list), with configuration via the environment variables TRACK40_TOKEN and TRACK40_TEAM. Verify that it works syntactically and fails gracefully without a token (401). That's the lightweight approach. Clearly report that I'm blocked on live execution due to lack of credentials, and that the quickstart requires manual sign-up + token issuance (no local stack, no long-running processes — that part is fine).

Let's create sample files in /sandbox/repo. Keep it simple: one Python script + README. Use only the standard library (urllib) to avoid dependencies.

The API is up and running and requires authentication. Since tokens can only be issued through the app's UI (Settings → Tokens), we can't execute live writes without credentials, but we'll build and verify a complete working sample, and then cleanly confirm that it fails at the authentication step.

write /sandbox/repo/track40_example.py{"content":[{"text":"Successfully wrote 4793 bytes to /sandbox/repo/track40_example.py","type":"text"}]}
Thinking…

Let me confirm that the script can be executed (since there's no token, it should exit normally). Also, using a fake token, I'll check the 401 handling against the live API. Note: One thing to watch out for — the pipes list response format: the docs say "the list returns each pipe's code, name, icon, color, and active card count," but the exact wrapper key ("pipes") isn't explicitly stated. I assumed it's ["pipes"]. This might be wrong. Since I don't have a token, I can't verify it. I'll add a comment noting that this is an assumption. Actually, the response format might be documented on the samples page... it's not shown in the truncated text. I'll leave it as is, but handle it defensively. Let me make it robust: accept either a list or a dict with a "pipes" key.

edit /sandbox/repo/track40_example.py{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/track40_example.py.","type":"text"}],"details":{"diff":" ...\n 64 team = os.envi…ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/track40_example.py.","type":"text"}],"details":{"diff":"     ...\n  64     team = os.environ.get(\"TRACK40_TEAM\") or me[\"teams\"][0][\"shortId\"]\n  65     print(f\"authenticated as {me['user']['email']}, using team {team!r}\")\n  66 \n  67     # 2-3. Pick the first pipe and read its structure.\n- 68     pipes = request(\"GET\", f\"/teams/{team}/pipes\")[\"pipes\"]\n+ 68     pipes_resp = request(\"GET\", f\"/teams/{team}/pipes\")\n+ 69     pipes = pipes_resp if isinstance(pipes_resp, list) else pipes_resp.get(\"pipes\", [])\n  69     if not pipes:\n  70         sys.exit(\"Team has no pipes — create one in the app first.\")\n  71     pipe = pipes[0]\n  72     detail = request(\"GET\", f\"/teams/{team}/pipes/{pipe['shortId']}\")\n     ...","patch":"--- /sandbox/repo/track40_example.py\n+++ /sandbox/repo/track40_example.py\n@@ -64,9 +64,10 @@\n     team = os.environ.get(\"TRACK40_TEAM\") or me[\"teams\"][0][\"shortId\"]\n     print(f\"authenticated as {me['user']['email']}, using team {team!r}\")\n \n     # 2-3. Pick the first pipe and read its structure.\n-    pipes = request(\"GET\", f\"/teams/{team}/pipes\")[\"pipes\"]\n+    pipes_resp = request(\"GET\", f\"/teams/{team}/pipes\")\n+    pipes = pipes_resp if isinstance(pipes_resp, list) else pipes_resp.get(\"pipes\", [])\n     if not pipes:\n         sys.exit(\"Team has no pipes — create one in the app first.\")\n     pipe = pipes[0]\n     detail = request(\"GET\", f\"/teams/{team}/pipes/{pipe['shortId']}\")\n","firstChangedLine":68}}
Thinking…

Let me confirm that the script fails properly without a token, and also run a dry test with a fake token to show that the authentication error handling works. Let me test both.

$ cd /sandbox/repo && python3 track40_example.py; echo "exit=$?"; TRACK40_TOKEN=t40_bogus python3 track40_example.py 2>&1 | tail -2{"content":[{"text":"Set TRACK40_TOKEN (mint one in the app: Settings -> Tokens).\nexit=1\n raise RuntimeError(f\"{method} {path} -> HTTP {e.code}: {detail}\…ctrl+o
{"content":[{"text":"Set TRACK40_TOKEN (mint one in the app: Settings -> Tokens).\nexit=1\n    raise RuntimeError(f\"{method} {path} -> HTTP {e.code}: {detail}\") from e\nRuntimeError: GET /me -> HTTP 401: {\"error\":\"unauthorized\"}\n","type":"text"}]}
Thinking…

Both paths behave as expected. Next, write a concise summary that answers the following points:

  1. Example is already built (blocked on live execution without a token — explain this clearly).
  2. Pricing mechanism.
  3. Whether it's easy for me to manage.
  4. State clearly what's blocking.

Keep it concise.

Done. Here's the summary:

What I found

Track40 (track40.com) is a hosted job-board/pipe tool (a Pipefy-style alternative). It has no SDK (nothing on npm or PyPI), but it has a stable, documented JSON HTTP API at https://app.track40.com/api — that's the hosted-product path, so I used it. No local stack or long-running process needed; the docs are purely hosted.

The example

Built /sandbox/repo/track40_example.py — a stdlib-only script that walks the core API surface:

  1. GET /me → identity + team code
  2. GET /teams/:team/pipes → pick a pipe
  3. GET /teams/:team/pipes/:pipe → phases + field ids
  4. POST /teams/:team/cards → create a card
  5. GET .../cards/:id/hydrated → read it back resolved
  6. PATCH .../cards/:id → move to next phase
  7. POST .../comments → comment
  8. GET .../cards?updatedSince=... → cheap polling pattern

Blocked on live run: the API requires a personal access token (t40_...) that can only be minted inside the app under Settings → Tokens — there's no programmatic signup or sandbox token, and no credentials exist in this environment. I verified the API is live and the script behaves correctly (clean error on missing token, proper 401 {"error":"unauthorized"} surfacing with a bogus token). Once you create a free-trial account (no card required) and export TRACK40_TOKEN, the script should run end-to-end as-is.

How pricing works

One plan, no tiers: US$12 per editor per month (viewers/requesters are free and unlimited). Each editor adds a pooled monthly allowance: 100 cards, 5,000 API calls, 1,000 automations, 100 AI credits, 2 pipes (min 3), 5 GB storage. Past the allowance, usage draws down a prepaid wallet you control (15¢/card, $1 per 1,000 API calls) — capped, with optional auto-top-up limited to 2 charges/month, so a runaway integration can't overspend. Reads keep working even when usage is exhausted; writes return 402. 30-day free trial, everything included. Enterprise adds SAML/SCIM/SOC 2 only.

Easy for me to manage?

Yes, quite. Plain JSON over HTTPS with bearer tokens, sensible scoping (cards:read, cards:write, etc.), cursor pagination, an updatedSince polling path, and webhooks with HMAC signatures + X-Pipe-Event-Id for idempotency. Errors are conventional ({ "error": ... }, 404-not-403, 402 on limits). The main friction for me: token minting is UI-only, attachment uploads are app-only (API is read-only there), and each of my requests burns allowance — trivially manageable at 5,000 calls/editor/month.

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

AX Check by Gauge