← Connector Tax
API
Tokens

Run Connector Tax from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page gates the demand evidence, reads the platform archetype from customers' stacks, ranks the build paths by who absorbs the maintenance tax, audits every resource for extraction and drafts the honest sync wording, then hands it all to the run as facts. The run writes the connector strategy or the business case; the page then holds it to its own facts (cxreply.js).

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"connector-tax"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, vendor, resources, facts) is missing. The page always sends all five of task, vendor, resources, carry, facts; carry is an empty string for strategy.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both lanes are metered and need a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Two lanes, chosen by task, in the order the work runs - whether, where and how, then the case for finance:

A missing or unknown task is answered as strategy. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the strategy's decisions into carry for the case lane.

The input

FieldRequiredWhat
taskyesstrategy or case. If it is missing or neither, do strategy and set "task": "strategy".
vendoryesthe product and the ask, customers' stacks, platforms planned, the managed-queue and schema facts, the owner, effort, build estimate, change rate, deadline and mandate, sync facts, the revenue-share line and the current customer-facing claim; "not given" means not given.
resourcesyesthe demand evidence (each artifact, marked when routed out or not verifiable) and every resource with its checklist answers and scope.
carrysend it (empty for strategy)case task only: the strategy's decisions, when the strategy task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the verdict and the ranked paths. A line starting "must:" applies to your reply.
retry_notenooptional; if present, your previous reply was unusable - follow it.

The rules the model applies

From the source skill, @samber/etl-connector-strategy - the only standard the run applies:

House rules

The replies

strategy

Write the connector strategy document.

  1. summary - one sentence, at most 40 words: the decision and the first thing that must happen.
  2. demand - verdict (one sentence on whether the evidence clears the gate), evidence (short strings naming each artifact that counts by its kind, platform and date - facts unchanged, wording shortened), reopen (one sentence: the signal that would reopen the decision; required when the recommendation is notyet; a fix-first verdict is a build decision with conditions, so leave it empty).
  3. platform - archetype (curated, open, tap or unknown, exactly as the facts read it), first (one sentence: the platform to target first, as the evidence names it, or what to find out), second_after (one sentence: what must be measured before a second).
  4. candidates - two or three objects (different paths on the one platform the evidence names are fine): name, platform, path (managed, sdk, custom or notyet), scope (which resources), tradeoff (one sentence: time to catalog presence, who owns the tax, control retained - only what the input says about that platform, never a program it does not mention). One candidate carries the recommended path.
  5. recommendation - path (exactly the path the must line names), why (one sentence naming the moves that ranked it).
  6. readiness - one object per fix, exactly the facts' list and order: resource (as written), gap (cursor, bulk, deletes, ratelimit or versioning), fix (one sentence: the API change). An empty array when the facts say so.
  7. cdc - language (the sync wording customers will read: incremental, the poll interval, eventually consistent, what it misses), ladder (short strings: what this vendor ships now and next, in order).
  8. certification - target (one sentence), clock (one sentence: the release-phase or contribution commitment), slo (one sentence including the 95% sync-success bar).
  9. maintenance - owner (the owning team's name if the input gives one; if it only says a funded team exists, that description; if nobody owns it, "unassigned - name one"), budget (one sentence giving the percentage range the facts state, and weeks when a build estimate is given), drivers (short strings), monitoring (one sentence: which of this vendor's resources and failure signals page whom, so a break is caught before a customer reports it).
  10. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "strategy",
  "summary": "one sentence",
  "demand": { "verdict": "one sentence", "evidence": [ "an artifact" ], "reopen": "" },
  "platform": { "archetype": "curated", "first": "one sentence", "second_after": "one sentence" },
  "candidates": [ { "name": "a short name", "platform": "a platform", "path": "managed", "scope": "which resources", "tradeoff": "one sentence" } ],
  "recommendation": { "path": "managed", "why": "one sentence" },
  "readiness": [ { "resource": "as written", "gap": "deletes", "fix": "one sentence" } ],
  "cdc": { "language": "the sync wording", "ladder": [ "a rung" ] },
  "certification": { "target": "one sentence", "clock": "one sentence", "slo": "one sentence" },
  "maintenance": { "owner": "a team", "budget": "one sentence", "drivers": [ "a driver" ], "monitoring": "one sentence" },
  "caveats": [ ]
}

The page holds it to its own facts (check() in cxreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no URL; no backticks; a summary of at most 40 words; no revenue-share income; the page's recommended path (or not building yet, with a reopen signal); the platform archetype; two or three candidates, each with a platform, a path, a scope and a trade-off, one carrying the recommended path; exactly the page's API fixes in order, each with its fix; sync language that says incremental, the poll interval, eventually consistent and what polling misses, never log-based or real time; a 95% sync-success SLO; the upkeep range; the owning team, or a call to name one; monitoring; at most four caveats.

case

Write the business case for finance, and the sync page marketing may publish.

  1. summary - one sentence, at most 40 words.
  2. decision - recommend (one sentence: what the case asks for - or, when the facts say do not build yet, that it recommends not building yet), reopen (one sentence: the reopen signal when not building yet; a fix-first verdict is a build decision with conditions, so leave it empty).
  3. drivers - objects: driver (deflection, parity, distribution or relief), evidence (one sentence citing an artifact the evidence lists, by its kind, platform or date). Only drivers the evidence supports; an empty array when nothing counts.
  4. costs - build (one sentence), upkeep (one sentence giving the percentage range the facts state, and weeks when a build estimate is given), owner (the owning team's name if the input gives one; if it only says a funded team exists, that description; if nobody owns it, "unassigned - name one").
  5. payoff - short strings, ranked, each opening with its category name and then saying what it means for this vendor: support deflection and retention first, then competitive parity, then co-marketing, and revenue share last, said to be essentially nonexistent. When the input says nothing about a tier, say it is not part of this case rather than asserting an absence.
  6. sync_page - headline (short), body (two or three sentences: incremental, the poll interval, eventually consistent, what it misses), misses (short strings).
  7. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "case",
  "summary": "one sentence",
  "decision": { "recommend": "one sentence", "reopen": "" },
  "drivers": [ { "driver": "deflection", "evidence": "one sentence" } ],
  "costs": { "build": "one sentence", "upkeep": "one sentence", "owner": "a team" },
  "payoff": [ "support deflection and retention", "competitive parity", "co-marketing", "revenue share - essentially nonexistent" ],
  "sync_page": { "headline": "short", "body": "two or three sentences", "misses": [ "what it misses" ] },
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules and no revenue-share income; when the page says not to build yet, a recommendation not to and a reopen signal; only deflection, parity, distribution or relief as drivers, each citing an artifact from the evidence; the build cost and the upkeep range; the owner as above; a payoff ranked with deflection first and revenue share last; an honest sync page that names what it misses.

Worked examples

Three runs from the page's free samples (invented companies): the strategy for an invoicing API racing a deal deadline, the business case for the same vendor carrying that strategy, and the strategy for a scheduling SaaS acting on a hunch. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.

strategy - An invoicing API racing a deal deadline

The page read it as: fix the API and the plan before the build; paths managed, sdk, custom (recommended: managed); archetype curated; API fixes Customers bulk, Invoices deletes, Customers deletes; 2 of 5 gates; failing checks: readiness, honesty, owner.

Input (facts is 3052 characters; the first 1392 are shown):

{
 "task": "strategy",
 "vendor": "Vendor: Tallyfox\nThe product and the ask: An invoicing and payments API for B2B SaaS finance teams; sales wants us listed in the Pipewell catalog to unblock a renewal.\nCustomers' data stacks: Managed commercial ELT; platforms planned at launch: 1; a platform covers the need: yes; the managed queue rejected the source: no; API too custom for a declarative layer: no; dynamic schema: no; team fluent in the platform SDK: no\nOwner in month 13: A side-task on an existing team; up-front weeks available: 6; hours a month after launch: 10; estimated build: 8 weeks; API change rate: low; deadline: yes; a standing distribution asset\nSync: poll interval 30 minutes; webhooks: yes; vendor-side deletion feed: no; customers report missed deletes: no\nBusiness case books revenue-share income: no; current customer-facing claim: \"Real-time CDC support for your warehouse\"; as-of: 2026-11-01",
 "resources": "Demand evidence (kind | platform | what it shows | date):\n- a support ticket | platform: Pipewell | finance team at a 400-seat customer asks when invoices will sync into Pipewell | 2026-09-14\n- a support ticket | platform: Pipewell | second customer asks for a Pipewell source for payments | 2026-10-02\n- a lost-deal or churn note | platform: Pipewell | renewal at risk: the buyer's data team standardised on Pipewell and will not maintain a custom script | 2026-10-20\n\nResources (cursor | bulk | deletes | 429 | versioning | scope):\n- Invoices | cursor stable | bulk yes | deletes no | 429 Retry-After yes | versioning yes | GAPS: deletes\n- Customers | cursor updated_at | bulk no | deletes no | 429 Retry-After yes | versioning yes | GAPS: bulk, deletes\n- Payments | cursor stable | bulk yes | deletes yes | 429 Retry-After yes | versioning yes | ready\n- Audit log | cursor none | bulk no | deletes no | 429 Retry-After no | versioning no | EXCLUDED - internal-only events, never exposed to customers",
 "carry": "",
 "facts": "The page's checks (3 fail, 2 warn, 2 pass, 4 n/a); verdict: fix the API and the plan before the build:\n- Demand and platform | evidence | pass | 2 support tickets, 1 lost-deal note - platforms named: Pipewell\n- Demand and platform | stacks | pass | Managed commercial ELT - A curated managed-ELT platform - gatekeeps entry with a multi-week review that can reject the source\n- Demand and platform | path | na | Platform-managed listing > Vendor-SDK connector > Custom outside-platform connector - a hard deadline promotes the platform-managed queue\n- The extraction API | readiness | fail | Invoices: missing a delete or change signal; Customers: missing a bulk/backfill path apart from UI pagination, a delete or change signal\n- The extraction API | deletes | warn | Invoices, Customers - the most common gap; connectors silently miss these deletes\n- The extraction API | cursor | warn | Customers rely on updated_at - keep it reliable and indexed, or publish an opaque cursor\n- CDC, certification and upkeep | honesty | fail | the claim \"Real-time CDC support for your warehouse\" says CDC without the incremental-sync scoping; drops the eventual-consistency framing; promises real time from a polled source\n- CDC, certification and upkeep | cdc | na | Incremental sync: changes are polled every 30 minutes, so synced data is eventually consistent. A record changed more than once within one\n\n[... 1660 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "strategy",
 "summary": "Apply for a Pipewell-managed listing to save the at-risk renewal, but first ship a Customers backfill path and a deletion signal for Invoices and Customers, and name the team that owns the connector in month 13.",
 "demand": {
  "verdict": "The gate clears: three dated Pipewell artifacts, including a renewal at risk because the buyer's data team will not maintain a custom script, show customers already asking for this connector.",
  "evidence": [
   "Support ticket, 2026-09-14: a 400-seat customer's finance team asks when invoices will sync into Pipewell",
   "Support ticket, 2026-10-02: a second customer asks for a Pipewell source for payments",
   "Lost-deal note, 2026-10-20: renewal at risk because the buyer's data team standardised on Pipewell and will not maintain a custom script"
  ],
  "reopen": ""
 },
 "platform": {
  "archetype": "curated",
  "first": "Pipewell first, because every ticket and the at-risk renewal name it, and its multi-week review can still reject Tallyfox, so the readiness fixes must land before applying.",
  "second_after": "Consider a second platform only after the Pipewell connector's real upkeep against the 10 hours a month is measured over live customer syncs."
 },
 "candidates": [
  {
   "name": "Pipewell-managed listing",
   "platform": "Pipewell",
   "path": "managed",
   "scope": "Invoices, Customers, Payments; Audit log excluded",
   "tradeoff": "Fastest route into the Pipewell catalog for the renewal, and Pipewell carries the upkeep, but Tallyfox gives up control of scope and release cadence and the review can still say no."
  },
  {
   "name": "Tallyfox-built Pipewell SDK connector",
   "platform": "Pipewell",
   "path": "sdk",
   "scope": "Invoices, Customers, Payments; Audit log excluded",
   "tradeoff": "Pipewell hosts it and Tallyfox keeps control, but Tallyfox fixes every break, the team is not fluent in the SDK, and an 8-week build overruns the 6 weeks available before the deadline."
  },
  {
   "name": "Custom connector outside Pipewell",
   "platform": "Outside any platform, run by Tallyfox",
   "path": "custom",
   "scope": "Invoices, Customers, Payments; Audit log excluded",
   "tradeoff": "Full control but no Pipewell catalog presence, which is exactly what the at-risk buyer asked for, and Tallyfox carries all of the upkeep and the decay risk."
  }
 ],
 "recommendation": {
  "path": "managed",
  "why": "Pipewell's managed queue leads because the renewal deadline promotes it, Pipewell covers the need without rejecting the source, and the API is regular enough for its declarative layer, so neither SDK nor custom work earns its standing cost."
 },
 "readiness": [
  {
   "resource": "Customers",
   "gap": "bulk",
   "fix": "Add a bulk export or backfill endpoint for Customers so the first sync does not page through the UI listing."
  },
  {
   "resource": "Invoices",
   "gap": "deletes",
   "fix": "Expose deleted or voided invoices through a deletion signal, such as tombstone records filterable by updated_at, so the connector can propagate removals."
  },
  {
   "resource": "Customers",
   "gap": "deletes",
   "fix": "Expose deleted Customers through the same tombstone or deletion signal, and keep updated_at reliable and indexed or publish an opaque cursor."
  }
 ],
 "cdc": {
  "language": "Incremental sync: Tallyfox changes are polled every 30 minutes, so data in your warehouse is eventually consistent. An invoice or customer changed more than once within one interval arrives only in its latest state, and hard deletes are [... 150 more characters cut from this display]",
  "ladder": [
   "Now: incremental polling every 30 minutes on the Invoices, Customers and Payments cursors",
   "Now: webhooks as a freshness supplement between polls, never a backfill",
   "Now: periodic full re-import to reconcile hard deletes",
   "Next: a Tallyfox-side deletion feed covering Invoices and Customers"
  ]
 },
 "certification": {
  "target": "A Pipewell managed-tier listing, earned on reliability after real customer syncs: one solid Tallyfox connector beats a broad, flaky one.",
  "clock": "Once listed, Pipewell's promotion from private preview to beta becomes a schedule Tallyfox must keep, so budget engineering time for it.",
  "slo": "Adopt at least 95% sync success on Pipewell as Tallyfox's own SLO and report against it from the private preview onward."
 },
 "maintenance": {
  "owner": "unassigned - name one",
  "budget": "Plan on 15-30% of the 8-week build a year, about 1.2-2.4 engineering weeks for this one connector, owned by a named team rather than a 10-hour-a-month side-task.",
  "drivers": [
   "Authentication changes",
   "Schema drift in Invoices, Customers and Payments",
   "Rate-limit shifts",
   "Pricing and compliance overhead"
  ],
  "monitoring": "Alert the owning team on failed Pipewell syncs and on schema drift, rather than waiting for finance teams to file tickets."
 },
 "caveats": [
  "Retire the current real-time warehouse claim; Tallyfox polls every 30 minutes and cannot promise real time.",
  "The connector is a cost center bought to keep renewals like the 2026-10-20 one; book no platform income for it.",
  "A side-task owner is the usual way connectors decay; name the team before applying to Pipewell."
 ]
}

Cut from this display only (the saved reply and the check use the full text): cdc.language (386 characters, 236 shown).

What step 5 prints for it: managed: Apply for a Pipewell-managed listing to save the at-risk renewal, but first ship a Customers backfill path and a deletion signal for Invoices and Customers, and name the team that owns the connector in month 13.

The page's check: kept - nothing to flag

case - An invoicing API racing a deal deadline

The input carries the saved strategy's decisions (R.laneForm fills carry from the strategy reply), as the page's handoff does. Upkeep: about 1.2-2.4 engineering weeks a year for 1 connector (15-30% of a 8-week build, each connector its own obligation).

Input (facts is 2973 characters; the first 1392 are shown):

{
 "task": "case",
 "vendor": "Vendor: Tallyfox\nThe product and the ask: An invoicing and payments API for B2B SaaS finance teams; sales wants us listed in the Pipewell catalog to unblock a renewal.\nCustomers' data stacks: Managed commercial ELT; platforms planned at launch: 1; a platform covers the need: yes; the managed queue rejected the source: no; API too custom for a declarative layer: no; dynamic schema: no; team fluent in the platform SDK: no\nOwner in month 13: A side-task on an existing team; up-front weeks available: 6; hours a month after launch: 10; estimated build: 8 weeks; API change rate: low; deadline: yes; a standing distribution asset\nSync: poll interval 30 minutes; webhooks: yes; vendor-side deletion feed: no; customers report missed deletes: no\nBusiness case books revenue-share income: no; current customer-facing claim: \"Real-time CDC support for your warehouse\"; as-of: 2026-11-01",
 "resources": "Demand evidence (kind | platform | what it shows | date):\n- a support ticket | platform: Pipewell | finance team at a 400-seat customer asks when invoices will sync into Pipewell | 2026-09-14\n- a support ticket | platform: Pipewell | second customer asks for a Pipewell source for payments | 2026-10-02\n- a lost-deal or churn note | platform: Pipewell | renewal at risk: the buyer's data team standardised on Pipewell and will not maintain a custom script | 2026-10-20\n\nResources (cursor | bulk | deletes | 429 | versioning | scope):\n- Invoices | cursor stable | bulk yes | deletes no | 429 Retry-After yes | versioning yes | GAPS: deletes\n- Customers | cursor updated_at | bulk no | deletes no | 429 Retry-After yes | versioning yes | GAPS: bulk, deletes\n- Payments | cursor stable | bulk yes | deletes yes | 429 Retry-After yes | versioning yes | ready\n- Audit log | cursor none | bulk no | deletes no | 429 Retry-After no | versioning no | EXCLUDED - internal-only events, never exposed to customers",
 "carry": "Recommendation: Platform-managed listing - Pipewell's managed queue leads because the renewal deadline promotes it, Pipewell covers the need without rejecting the source, and the API is regular enough for its declarative layer, so neither SDK nor custom work earns its standing cost.\nPlatform: curated - Pipewell first, because every ticket and the at-risk renewal name it, and its multi-week review can still reject Tallyfox, so the readiness fixes must land before applying.\nReadiness fixes: Customers bulk, Invoices deletes, Customers deletes\nSync language: Incremental sync: Tallyfox changes are polled every 30 minutes, so data in your warehouse is eventually consistent. An invoice or customer changed more than once within one interval arrives only in its latest state, and hard deletes are not captured by polling until a periodic full re-import reconciles them. Webhooks add freshness between polls but never replace the initial backfill.\nCertification: A Pipewell managed-tier listing, earned on reliability after real customer syncs: one solid Tallyfox connector beats a broad, flaky one.; SLO: Adopt at least 95% sync success on Pipewell as Tallyfox's own SLO and report against it from the private preview onward.\nMaintenance: owner unassigned - name one; budget Plan on 15-30% of the 8-week build a year, about 1.2-2.4 engineering weeks for this one connector, owned by a named team rather than a 10-hour-a-month side-task.",
 "facts": "The page's checks (3 fail, 2 warn, 2 pass, 4 n/a); verdict: fix the API and the plan before the build:\n- Demand and platform | evidence | pass | 2 support tickets, 1 lost-deal note - platforms named: Pipewell\n- Demand and platform | stacks | pass | Managed commercial ELT - A curated managed-ELT platform - gatekeeps entry with a multi-week review that can reject the source\n- Demand and platform | path | na | Platform-managed listing > Vendor-SDK connector > Custom outside-platform connector - a hard deadline promotes the platform-managed queue\n- The extraction API | readiness | fail | Invoices: missing a delete or change signal; Customers: missing a bulk/backfill path apart from UI pagination, a delete or change signal\n- The extraction API | deletes | warn | Invoices, Customers - the most common gap; connectors silently miss these deletes\n- The extraction API | cursor | warn | Customers rely on updated_at - keep it reliable and indexed, or publish an opaque cursor\n- CDC, certification and upkeep | honesty | fail | the claim \"Real-time CDC support for your warehouse\" says CDC without the incremental-sync scoping; drops the eventual-consistency framing; promises real time from a polled source\n- CDC, certification and upkeep | cdc | na | Incremental sync: changes are polled every 30 minutes, so synced data is eventually consistent. A record changed more than once within one\n\n[... 1581 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "case",
 "summary": "Fund a Pipewell managed listing to save the at-risk renewal, but only after Customers gains a bulk path and Invoices and Customers gain a delete signal, and after a team is named to own the connector.",
 "decision": {
  "recommend": "Approve an 8-week effort toward a Pipewell managed-queue listing, sequenced so the Customers bulk fix and the Invoices and Customers delete fixes land before Tallyfox applies to Pipewell's review, which can still reject the source.",
  "reopen": ""
 },
 "drivers": [
  {
   "driver": "deflection",
   "evidence": "Two support tickets, dated 2026-09-14 and 2026-10-02, ask when invoices and payments will sync into Pipewell; a maintained Pipewell source answers that question once instead of per ticket."
  },
  {
   "driver": "parity",
   "evidence": "The lost-deal note of 2026-10-20 puts a renewal at risk because the buyer's data team standardised on Pipewell, so a Pipewell source is table stakes in that evaluation."
  },
  {
   "driver": "relief",
   "evidence": "The same 2026-10-20 renewal note says the buyer's data team will not maintain a custom script, so a listed connector takes that pipeline work off them."
  }
 ],
 "costs": {
  "build": "About 8 engineering weeks for one Pipewell connector covering Invoices, Customers and Payments, plus the API fixes to Customers and Invoices first; the Audit log stays out as internal-only",
  "upkeep": "15-30% of the 8-week build every year, about 1.2-2.4 engineering weeks for this one connector, which no later connector will share",
  "owner": "unassigned - name one; a 10-hour-a-month side-task on an existing team is how this connector would decay"
 },
 "payoff": [
  "Support deflection and retention: fewer Pipewell sync tickets and the at-risk renewal kept",
  "Competitive parity in Pipewell-standardised data-stack evaluations",
  "Co-marketing alongside Pipewell once listed",
  "Revenue share - essentially nonexistent; Pipewell will not pay Tallyfox, and none is booked"
 ],
 "sync_page": {
  "headline": "Tallyfox to Pipewell: incremental sync every 30 minutes",
  "body": "Tallyfox offers incremental sync: invoices, customers and payments are polled every 30 minutes, so your warehouse is eventually consistent, not real time. A record changed more than once within one interval arrives only in its latest [... 208 more characters cut from this display]",
  "misses": [
   "Intermediate states when a record changes more than once within 30 minutes",
   "Hard deletes of invoices and customers until a periodic full re-import"
  ]
 },
 "caveats": [
  "Withdraw the current real-time claim on the site: a 30-minute poll is incremental and eventually consistent, not real time.",
  "Pipewell's multi-week review can reject Tallyfox, so the renewal deadline depends on the readiness fixes landing first.",
  "Once listed, the private-preview-to-beta clock becomes a schedule commitment, held to at least 95% sync success.",
  "Customers relies on updated_at as its cursor; keep it reliable and indexed or publish an opaque cursor."
 ]
}

Cut from this display only (the saved reply and the check use the full text): sync_page.body (441 characters, 233 shown).

What step 5 prints for it: deflection Two support tickets, dated 2026-09-14 and 2026-10-02, ask when invoices and payments will sync into Pipewell; a maintained Pipewell source answers that question once instead of per ticket. | parity The lost-deal note of 2026-10-20 puts a renewal at risk because the buyer's data team standardised on Pipewell, so a Pipewell source is table stakes in that evaluation. | relief The same 2026-10-20 renewal note says the buyer's data team will not maintain a custom script, so a listed connector takes that pipeline work off them.

The page's check: kept - nothing to flag

strategy - A scheduling SaaS acting on a hunch

The page read it as: do not build yet - the demand is a hypothesis; paths managed, sdk, custom (recommended: notyet); archetype unknown; API fixes Employees cursor, Shifts bulk, Employees bulk, Shifts deletes, Employees deletes, Shifts versioning, Employees versioning; 0 of 5 gates; failing checks: evidence, stacks, readiness, honesty, owner.

Input (facts is 3437 characters; the first 1396 are shown):

{
 "task": "strategy",
 "vendor": "Vendor: Hivegrid\nThe product and the ask: A shift-scheduling SaaS; the CEO read that rivals have ETL connectors and wants one this quarter.\nCustomers' data stacks: Unknown; platforms planned at launch: 2; a platform covers the need: yes; the managed queue rejected the source: no; API too custom for a declarative layer: no; dynamic schema: no; team fluent in the platform SDK: no\nOwner in month 13: Nobody yet; up-front weeks available: 4; hours a month after launch: 4; estimated build: 6 weeks; API change rate: low; deadline: no; a one-off checkbox for a deal\nSync: poll interval not given; webhooks: no; vendor-side deletion feed: no; customers report missed deletes: no\nBusiness case books revenue-share income: yes; current customer-facing claim: none; as-of: 2026-11-01",
 "resources": "Demand evidence (kind | platform | what it shows | date):\n- an internal hypothesis | platform: none | leadership believes a connector would help enterprise deals | 2026-10-01 | NOT VERIFIABLE\n- a support ticket | platform: none | a customer asks us to drop nightly parquet files in their S3 bucket | 2026-10-12 | ROUTED OUT - bulk data sharing (files in a bucket or lake)\n\nResources (cursor | bulk | deletes | 429 | versioning | scope):\n- Shifts | cursor updated_at | bulk no | deletes no | 429 Retry-After yes | versioning no | GAPS: bulk, deletes, versioning\n- Employees | cursor none | bulk no | deletes no | 429 Retry-After yes | versioning no | GAPS: cursor, bulk, deletes, versioning",
 "carry": "",
 "facts": "The page's checks (5 fail, 4 warn, 0 pass, 4 n/a); verdict: do not build yet - the demand is a hypothesis:\n- Demand and platform | evidence | fail | no verifiable artifact - a hypothesis - approve no build; reopen on three named-platform tickets in a quarter, one lost deal citing the gap, or API logs of hand-rolled extraction\n- Demand and platform | routed | warn | a customer asks us to drop nightly parquet files in their S3 bucket - bulk data sharing (files in a bucket or lake)\n- Demand and platform | stacks | fail | suspend the workflow - survey customers or read the integration-request tickets before picking a platform\n- Demand and platform | platforms | warn | 2 platforms planned at launch - ship one, measure its maintenance load, then add a second\n- Demand and platform | path | na | Platform-managed listing > Vendor-SDK connector > Custom outside-platform connector - but with no verifiable demand the recommendation is not to build yet\n- The extraction API | readiness | fail | Shifts: missing a bulk/backfill path apart from UI pagination, a delete or change signal, additive versioning with published sunset dates; Employees: missing a stable incremental cursor, a bulk/backfill path apart from UI pagination, a delete or change signal, additive versioning with published sunset dates\n- The extraction API | deletes | warn | Shifts, Employees - the most common gap; connectors\n\n[... 2041 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "strategy",
 "summary": "Do not build a Hivegrid connector this quarter: the demand is a leadership hypothesis, so fix the Shifts and Employees API first and survey customers' data stacks before choosing any platform.",
 "demand": {
  "verdict": "The gate fails: the only connector evidence is an unverifiable leadership belief from 2026-10-01, and the one customer ticket asks for nightly parquet files in their S3 bucket, which is bulk data sharing rather than a connector.",
  "evidence": [],
  "reopen": "Reopen when three support tickets in one quarter name a specific ETL platform, a lost deal cites the missing connector, or API logs show customers hand-rolling extraction of Shifts or Employees."
 },
 "platform": {
  "archetype": "unknown",
  "first": "No platform can be chosen yet: survey customers or read the integration-request tickets to learn which ETL platforms Hivegrid's customers actually run.",
  "second_after": "Plan one platform, not the two pencilled in for launch, and add a second only after the first connector's upkeep against the 4 hours a month is measured."
 },
 "candidates": [
  {
   "name": "Not yet: improve the raw API",
   "platform": "None until the stack survey lands",
   "path": "notyet",
   "scope": "Shifts and Employees, through the public API",
   "tradeoff": "No catalog presence this quarter, but no connector upkeep either, and the cursor, bulk, deletion and versioning fixes make any later connector cheaper while helping customers who extract by hand today."
  },
  {
   "name": "Platform-managed listing, once demand is proven",
   "platform": "The platform the stack survey names",
   "path": "managed",
   "scope": "Shifts and Employees, after the readiness fixes",
   "tradeoff": "The platform would carry the upkeep in exchange for Hivegrid's control of scope and cadence, but with no named platform and no verifiable demand there is nothing to apply for yet."
  }
 ],
 "recommendation": {
  "path": "notyet",
  "why": "Without a verifiable artifact the demand gate blocks every build path, and the unknown customer stack suspends the platform choice, so the managed, SDK and custom rungs all wait."
 },
 "readiness": [
  {
   "resource": "Employees",
   "gap": "cursor",
   "fix": "Add a stable incremental cursor to Employees, either a reliable, indexed updated_at or an opaque cursor that holds across syncs."
  },
  {
   "resource": "Shifts",
   "gap": "bulk",
   "fix": "Add a bulk export or backfill endpoint for Shifts so a first sync does not page through the UI listing."
  },
  {
   "resource": "Employees",
   "gap": "bulk",
   "fix": "Add a bulk export or backfill endpoint for Employees, separate from UI pagination."
  },
  {
   "resource": "Shifts",
   "gap": "deletes",
   "fix": "Publish a deletion or change signal for Shifts, such as tombstone records returned by the cursor, so removed shifts can be propagated."
  },
  {
   "resource": "Employees",
   "gap": "deletes",
   "fix": "Publish the same deletion or change signal for Employees so departed or removed employees are not silently kept downstream."
  },
  {
   "resource": "Shifts",
   "gap": "versioning",
   "fix": "Version the Shifts API additively and publish sunset dates before any breaking change."
  },
  {
   "resource": "Employees",
   "gap": "versioning",
   "fix": "Version the Employees API additively with published sunset dates."
  }
 ],
 "cdc": {
  "language": "Incremental sync: Hivegrid changes will be polled on a fixed interval still to be set, so synced data is eventually consistent. A shift or employee record changed more than once within one interval arrives only in its latest state, and [... 90 more characters cut from this display]",
  "ladder": [
   "Now: no connector; ship the cursor, bulk, deletion and versioning fixes in the public API",
   "If demand reopens: incremental polling on Shifts and Employees at an interval set before launch",
   "With it: a periodic full re-import to reconcile hard deletes",
   "Next: a Hivegrid-side deletion feed for Shifts and Employees"
  ]
 },
 "certification": {
  "target": "None until demand is proven and a platform is named; reliability on one connector comes before any listing.",
  "clock": "No release-phase or contribution commitment applies, since nothing is being listed this quarter.",
  "slo": "When a connector is approved, adopt at least 95% sync success as Hivegrid's own SLO from its first real syncs."
 },
 "maintenance": {
  "owner": "unassigned - name one",
  "budget": "If the two planned connectors were built, upkeep would run 15-30% of the 6-week build a year for each, about 1.8-3.6 engineering weeks a year in total, far beyond the 4 hours a month offered.",
  "drivers": [
   "Authentication changes",
   "Schema drift in Shifts and Employees",
   "Rate-limit shifts",
   "Pricing and compliance overhead"
  ],
  "monitoring": "Any future connector alerts its named owner on sync failures and schema drift instead of waiting for customer tickets."
 },
 "caveats": [
  "Strike the revenue-share line from the business case: no ETL platform pays source vendors, so argue support deflection and retention instead.",
  "Route the nightly S3 parquet request to a bulk data-sharing option, not a connector.",
  "Rivals having connectors is not demand; a one-off deal checkbox does not fund month-13 upkeep."
 ]
}

Cut from this display only (the saved reply and the check use the full text): cdc.language (325 characters, 235 shown).

What step 5 prints for it: notyet: Do not build a Hivegrid connector this quarter: the demand is a leadership hypothesis, so fix the Shifts and Employees API first and survey customers' data stacks before choosing any platform.

The page's check: kept - nothing to flag

Build the input yourself

Load the engine in node (global.window = global; require("./cxkit.js"); require("./cxreply.js")), then:

const K = ConnectorKit, R = ConnectorReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", buildcost: "8", poll: "30", capacity: "side", deadline: "yes",
  evidence: "support ticket | Pipewell | wants a source | 2026-10-01\nlost deal | Pipewell | buyer standardised on it | 2026-10-02",
  resources: "Orders | stable | yes | no | yes | yes | in\nLogs | none | no | no | no | no | out: internal" }));
// f.demandOk, f.archetype, f.rank (the ranked paths and moves), f.strategyRec, f.fixList (API fixes in order), f.upkeep, f.gate
// K.syncMd(f), K.readinessCsv(f) - the sync-page draft and the readiness table
const b = K.buildInput(f, "strategy");                       // or "case" (R.laneForm(form, "case", strategy) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "strategy");
// R.check(reply, b)                                         // {status: kept or flagged, notes}

Get a token for the steps below from /tokens.html.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN"   # from https://connector-tax.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"connector-tax"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "strategy", "vendor": "Vendor: Tallyfox
The product and the ask: ...", "resources": "Demand evidence (kind | platform | what it shows | date):
- a support ticket | platform: Pipewell | ...", "carry": "", "facts": "The page's checks (...); verdict: fix the API and the plan before the build:
- Demand and platform | evidence | pass | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses connector-tax:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A strategy reply carries summary, demand, platform, candidates, recommendation, readiness, cdc, certification, maintenance and caveats; a case reply carries summary, decision, drivers, costs, payoff, sync_page and caveats. The code below prints the gist of either: the recommended path and the summary, or each driver and its evidence.

KEY="connector-tax:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "case" then (.drivers[] | "\(.driver): \(.evidence)") 
       else "\(.recommendation.path): \(.summary)" end' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: connector-tax:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json