← RevOps Desk / API
Tokens

Drive RevOps Desk from your own code

The page does its free work in the browser: it reads a pasted lead process and its context, names the lifecycle stages and the MQL definition, reads every scoring rule with its points (fit, engagement, negative) and the threshold, the routing method and fallback owner, the SLA and the speed-to-lead, computes lead-to-MQL, MQL-to-SQL, SQL-to-opportunity and win rate against the revops skill's benchmarks, finds the six areas (lifecycle, scoring, routing, handoff, pipeline, data) and raises flags RO-01..RO-25. Its two paid lanes are available over HTTP. You send one JSON object with task set to "audit" or "spec", and the model replies with one JSON object. The easiest way to build the facts string is the page itself: RevKit.buildInput(lane, RevKit.analyze(process, {context, motion, question}), {auditHandoff}) in revopskit.js, which also runs under Node.

Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends Content-Type: application/json and, once you have a token, Authorization: Bearer <token>. Both lanes run on gpt-terra with a 10% publisher markup. A run needs a signed-in (user) token and is metered: the platform holds the estimate's hold_credits while the run is in flight and then charges only what it used (charged_credits). /estimate is free. A plain-text summary is at /llms.txt.

The response envelope

Every response is {"data": ..., "error": null} on success or {"data": null, "error": {"code", "message"}} on failure.

Error codes

HTTPcodemeaning
400VALIDATION_ERRORThe body is not a JSON object, or a field is the wrong type. Every field of the run input is a string.
401UNAUTHORIZEDNo token, or an expired one: get a new one (step 2).
402INSUFFICIENT_CREDITSThe balance is below the run's minimum. Top up at skillsafe.ai/account/billing/.
403FORBIDDENA guest token tried a metered run: sign in for a personal token.
404NOT_FOUNDUnknown job id.
429RATE_LIMITEDBack off and retry with the same Idempotency-Key.

The task field: two lanes

taskwhat the model does
"audit"The lead process audit: the process (motion, CRM, a one-line read); the six areas (lifecycle, scoring, routing, handoff, pipeline, data) each good, weak, bad or unknown with a finding, quotes and a fix; the funnel (the page's four rates, the speed-to-lead and the weakest stage); the issues as finding, impact and fix with the page's flag ids; 3 to 6 fixes; a verdict sound, fix_first or rebuild.
"spec"The RevOps spec to implement: lifecycle stages with entry, exit and owner; an mql_definition needing fit and engagement; scoring (fit, engagement with decay, negative, threshold, weighting, recalibration); routing (method, ordered rules, fallback, capacity); sla rows with escalations; recycling reason codes; pipeline stages with exit criteria and required fields; metrics; automations.

Input fields (all strings)

fieldwhencontent
taskrequiredaudit or spec
processrequiredThe lead process as pasted, or what the business has today when nothing is set up. Past 6,000 characters the page cuts the middle on line boundaries and leaves a [revops-desk: ...] marker.
factsrequiredA JSON string: motion, motion_basis, planned, crm, acv, cycle_days, stages, mql_definition, mql_any_download, areas (lifecycle, scoring, routing, handoff, pipeline, data: found, evidence, note), found, scoring, routing, handoff, pipeline, data, counts, metrics, bench, weakest, flags, notes. It is RevKit.factsOf(A).
contextoptionalThe business, the motion, the ACV and sales cycle, the CRM and the team. Cut past 4,000 characters.
motionoptionalThe go-to-market motion when the user chose it: plg, sales_led or hybrid.
questionoptionalWhat the user wants to know.
audit_handoffspec onlyA JSON string of an audit's verdict, bad[] (areas), issues[{flag, finding, fix}] and fixes[], for the same input.
retry_noteoptionalOnly on a reformat retry: what was wrong with the previous reply.

The reply contracts

Both lanes share an envelope: task, headline, questions[], summary.

task: "audit"

process{motion: plg|sales_led|hybrid|unclear, crm, read}, areas[{area: lifecycle|scoring|routing|handoff|pipeline|data, status: good|weak|bad|unknown, finding, evidence[], fix}] (exactly six, in order), funnel{lead_to_mql, mql_to_sql, sql_to_opp, win_rate, speed_to_lead, weakest: lead_to_mql|mql_to_sql|sql_to_opp|win_rate|none, read}, issues[{flag, kind: lifecycle|scoring|routing|handoff|pipeline|data|performance, severity: high|medium|low, finding, impact, evidence[], fix}], fixes[] (3 to 6), verdict (sound|fix_first|rebuild).

task: "spec"

source (audit handoff|from the input), motion, lifecycle[{stage: subscriber|lead|mql|sql|opportunity|customer|evangelist, entry, exit, owner}], mql_definition, scoring{fit[{attribute, points}], engagement[{signal, points, decay}], negative[{signal, points}], threshold, weighting, recalibration: monthly|quarterly|semi-annually}, routing{method: round_robin|territory|account|skill|hybrid, rules[], fallback, capacity}, sla[{handoff, target, escalation}], recycling[{code, reason, action}], pipeline[{stage, exit_criteria, required_fields[]}], metrics[{metric, formula, target}], automations[].

Worked examples

These are the page's own bundled examples (invented businesses and numbers), with the input as app.js builds it and the saved model reply the page replays for free. One worked example per lane, and the audit-to-spec handoff.

Audit: a logistics SaaS where one download makes an MQL and 18% are accepted

Input (process and facts shortened here):

{
 "task": "audit",
 "process": "How leads get from marketing to sales at Brindlecove today\n\nLifecycle: Subscriber -> Lead -> MQL -> Opportunity -> Customer.\nAn MQL is anyone who downloads a piece of gated content or registers for a webinar.\n\nLead scoring (set up in HubSpot in 2022, never changed since):\n- Ebook or whitepaper download: +25\n- Webinar registration: +20\n- Email click: +5\n- Any page visit: +2 per page view\n- Demo request: +20\n- MQL thresh... (998 characters in all)",
 "facts": "(a JSON string; decoded: motion hybrid, 6 stages named, 5 scoring rules (threshold 25), routing queue, speed-to-lead 2 days, 6 of 6 areas stated, lead-to-MQL 23.96%, MQL-to-SQL 18%, SQL-to-opportunity 46.38%, win rate 21.88%, weakest mql_to_sql, flags RO-03, RO-04, RO-07, RO-11, RO-15, RO-20, RO-06, RO-14, RO-16, RO-22, RO-09, RO-19, RO-21)",
 "context": "Brindlecove Analytics - reporting software for mid-market logistics companies (B2B SaaS, hybrid: free trial plus a sales team).\nACV around $18,000; sales cycle about 45 days.\nStack: HubSpot for marketing and the CRM; Calendly for demos. 4 SDRs and 6 AEs.\n",
 "question": "Sales says our MQLs are junk. Is it the scoring or the follow-up?"
}

Reply:

{
 "task": "audit",
 "process": {
  "motion": "hybrid",
  "crm": "HubSpot",
  "read": "Any gated download or webinar sign-up makes an MQL in HubSpot, MQLs wait in a shared inbox until an SDR picks them up about 2 days later, and rejected MQLs are closed as \"bad lead\" and never touched again."
 },
 "areas": [
  {
   "area": "lifecycle",
   "status": "bad",
   "finding": "The MQL is defined by one content action with no fit criteria, the lifecycle skips SQL even though sales accepts SQLs, and marketing and sales openly disagree on what a good lead is.",
   "evidence": [
    "An MQL is anyone who downloads a piece of gated content or registers for a webinar.",
    "Subscriber -> Lead -> MQL -> Opportunity -> Customer.",
    "Sales says the MQLs are junk and mostly students and job seekers"
   ],
   "fix": "Agree a written MQL definition with sales that needs fit (mid-market logistics company, relevant role) AND engagement, and add an SQL stage with entry and exit criteria and an owner."
  },
  {
   "area": "scoring",
   "status": "bad",
   "finding": "Every rule is engagement, nothing scores fit or subtracts bad signals, one download alone reaches the 25-point threshold, and a demo request scores less than an ebook.",
   "evidence": [
    "Ebook or whitepaper download: +25",
    "Demo request: +20",
    "MQL threshold: 25 points"
   ],
   "fix": "Rebuild the model as a 50/50 fit and engagement split with a threshold of 60-65, negative scoring, and pricing page and demo request weighted above content."
  },
  {
   "area": "routing",
   "status": "bad",
   "finding": "MQLs have no owner: they sit in a shared inbox until an SDR has time, with no fallback and no capacity rule.",
   "evidence": [
    "new MQLs land in a shared inbox in HubSpot and the SDRs grab them when they
... (shortened here; the page shows it whole)

Spec: the same business, carrying the audit

Input (with the audit handed over):

{
 "task": "spec",
 "process": "How leads get from marketing to sales at Brindlecove today\n\nLifecycle: Subscriber -> Lead -> MQL -> Opportunity -> Customer.\nAn MQL is anyone who downloads a piece of gated content or registers for a webinar.\n\nLead scoring (set up in HubSpot in 2022, never changed since):\n- Ebook or whitepaper download: +25\n- Webinar registration: +20\n- Email click: +5\n- Any page visit: +2 per page view\n- Demo request: +20\n- MQL thresh... (998 characters in all)",
 "facts": "(a JSON string; decoded: motion hybrid, 6 stages named, 5 scoring rules (threshold 25), routing queue, speed-to-lead 2 days, 6 of 6 areas stated, lead-to-MQL 23.96%, MQL-to-SQL 18%, SQL-to-opportunity 46.38%, win rate 21.88%, weakest mql_to_sql, flags RO-03, RO-04, RO-07, RO-11, RO-15, RO-20, RO-06, RO-14, RO-16, RO-22, RO-09, RO-19, RO-21)",
 "context": "Brindlecove Analytics - reporting software for mid-market logistics companies (B2B SaaS, hybrid: free trial plus a sales team).\nACV around $18,000; sales cycle about 45 days.\nStack: HubSpot for marketing and the CRM; Calendly for demos. 4 SDRs and 6 AEs.\n",
 "question": "Sales says our MQLs are junk. Is it the scoring or the follow-up?",
 "audit_handoff": "{\"verdict\":\"rebuild\",\"bad\":[\"lifecycle\",\"scoring\",\"routing\",\"handoff\"],\"issues\":[{\"flag\":\"RO-03\",\"finding\":\"Marketing and sales do not agree on what a qualified lead is.\",\"fix\":\"Write the MQL definition jointly with the SDR and AE leads, sign it off, and review it against rejection reasons each quarter.\"},{\"flag\":\"RO-0..."
}

Reply:

{
 "task": "spec",
 "source": "audit handoff",
 "motion": "hybrid",
 "lifecycle": [
  {
   "stage": "subscriber",
   "entry": "Signs up for the newsletter or blog updates without asking for anything else.",
   "exit": "Gives company details on a form, downloads content or starts a free trial.",
   "owner": "Marketing"
  },
  {
   "stage": "lead",
   "entry": "A known contact with a company email who has filled in a form, registered for a webinar or started a free trial.",
   "exit": "Reaches 65 points with fit and engagement both scored, or requests a demo from a fit company.",
   "owner": "Marketing"
  },
  {
   "stage": "mql",
   "entry": "Meets the MQL definition and is auto-assigned to one SDR in HubSpot.",
   "exit": "The SDR accepts it as an SQL or rejects it with a reason code within 48 hours.",
   "owner": "SDR"
  },
  {
   "stage": "sql",
   "entry": "The SDR has spoken to the contact and confirmed a logistics reporting need, a buyer role and interest in a demo.",
   "exit": "The AE holds discovery and creates an opportunity, or returns it with a reason code.",
   "owner": "SDR until discovery is booked, then AE"
  },
  {
   "stage": "opportunity",
   "entry": "The AE has run discovery and created a deal in the Discovery stage with the required fields filled.",
   "exit": "Closed-won or Closed-lost with a loss reason.",
   "owner": "AE"
  },
  {
   "stage": "customer",
   "entry": "The deal is Closed-won and the contract is signed.",
   "exit": "Becomes a reference or referral source, or churns.",
   "owner": "[account manager or CS owner]"
  }
 ],
 "mql_definition": "A contact at a mid-market logistics company in a buyer role (fit) who reaches 65 points through buying intent such as the pricing page, a free trial or repeat visits (engagement); a demo request b
... (shortened here; the page shows it whole)

Audit: an enterprise security vendor with sound scoring and stalled deals

Input:

{
 "task": "audit",
 "process": "Ostrevane Security lead process (Salesforce)\n\nStages: Lead -> MQL -> SAL -> SQL -> Opportunity -> Customer.\nMQL: a fit score of 40+ and an engagement score of 35+, so 75 points in total.\n\nFit scoring:\n- Company size 500+ employees: +20\n- Target industry (financial services, healthcare): +15\n- VP or above in security or IT: +20\n- Uses a competitor product: +10\nEngagement scoring:\n- Demo request: +30\n- Pricing page visite... (1386 characters in all)",
 "facts": "(a JSON string; decoded: motion sales_led, 6 stages named, 11 scoring rules (threshold 75), routing territory, speed-to-lead 4 business hours, 6 of 6 areas stated, lead-to-MQL 10.83%, MQL-to-SQL 45%, SQL-to-opportunity 63.25%, win rate 17.57%, weakest win_rate, flags RO-18, RO-15, RO-19, RO-23)",
 "context": "Ostrevane Security - cloud security posture management for enterprises (sales-led).\nACV $65K; sales cycle about 120 days. Salesforce is the CRM, Marketo for marketing automation.\n",
 "motion": "sales_led"
}

Reply:

{
 "task": "audit",
 "process": {
  "motion": "sales_led",
  "crm": "Salesforce",
  "read": "Marketo scores leads on fit and engagement, named accounts and territories route MQLs to an SDR who has 4 business hours to call and 48 hours to accept or reject, and accepted deals then move through six Salesforce pipeline stages."
 },
 "areas": [
  {
   "area": "lifecycle",
   "status": "good",
   "finding": "Six stages are named, including an SAL step where the SDR accepts or rejects, and the MQL needs both fit and engagement.",
   "evidence": [
    "Stages: Lead -> MQL -> SAL -> SQL -> Opportunity -> Customer",
    "a fit score of 40+ and an engagement score of 35+"
   ],
   "fix": ""
  },
  {
   "area": "scoring",
   "status": "weak",
   "finding": "The model has fit, engagement and negative rules and a quarterly recalibration, but a demo request alone (+30) cannot clear the 35-point engagement bar, so the strongest buying signal waits on a second action.",
   "evidence": [
    "Demo request: +30",
    "an engagement score of 35+"
   ],
   "fix": "Make a demo request from a lead who meets the fit bar an immediate MQL, outside the points threshold."
  },
  {
   "area": "routing",
   "status": "good",
   "finding": "Routing goes most specific first, named account then territory, with a team queue as the fallback owner.",
   "evidence": [
    "named accounts go to the account owner; otherwise leads are routed by territory",
    "the lead goes to the team queue with a 1-hour SLA"
   ],
   "fix": ""
  },
  {
   "area": "handoff",
   "status": "weak",
   "finding": "There is a first-contact SLA, an accept-or-reject deadline, an escalation and recycling with reason codes, but the SLA is 4 business hours and the first escalation only fires at 4 hours.",
   "evidence": [
    "the SD
... (shortened here; the page shows it whole)

Spec: a PLG notes app, from scratch

Input:

{
 "task": "spec",
 "process": "We don't have lead scoring or a lifecycle yet - starting from scratch.\nToday: people sign up for the free plan on their own. Our two AEs look at the signup list every Monday and email whoever looks like a big company.\nWhat we can see in the product: account created, onboarding finished, notes shared with a teammate, teammates invited, hitting the 3-project limit on the free plan, pricing page visits.\nWe want sales to... (509 characters in all)",
 "facts": "(a JSON string; decoded: motion plg, 0 stages named, 0 scoring rules (threshold none), routing queue, speed-to-lead unknown, 2 of 6 areas stated, lead-to-MQL unknown, MQL-to-SQL unknown, SQL-to-opportunity unknown, win rate unknown, weakest none, flags RO-11, RO-25)",
 "context": "Quillmesh Notes - a shared notes app for product teams (PLG, free plan, Team plan $10 per seat a month).\nAbout 3,000 signups a month. HubSpot free CRM. Two AEs, no SDRs.\n",
 "motion": "plg",
 "question": "Which signups should our two AEs call first?"
}

Reply:

{
 "task": "spec",
 "source": "from the input",
 "motion": "plg",
 "lifecycle": [
  {
   "stage": "subscriber",
   "entry": "A visitor joins the newsletter or blog list without creating an account.",
   "exit": "Creates a free Quillmesh Notes account.",
   "owner": "Marketing"
  },
  {
   "stage": "lead",
   "entry": "Account created on the free plan.",
   "exit": "Score reaches the MQL threshold, or the account is archived after no activity in 12+ months.",
   "owner": "Marketing (self-serve onboarding)"
  },
  {
   "stage": "mql",
   "entry": "Score reaches 60 with both fit and team engagement points, assigned to one named AE in HubSpot.",
   "exit": "The AE accepts it as an SQL or rejects it with a reason code within 48 hours.",
   "owner": "AE assigned by routing"
  },
  {
   "stage": "sql",
   "entry": "The AE has spoken with the workspace owner and confirmed a team that will need the paid plan.",
   "exit": "A Team plan conversation is opened as a deal, or the lead is recycled with a reason code.",
   "owner": "AE"
  },
  {
   "stage": "opportunity",
   "entry": "A HubSpot deal exists with seat count and decision maker recorded.",
   "exit": "Closed won on the Team plan or closed lost with a reason.",
   "owner": "AE"
  },
  {
   "stage": "customer",
   "entry": "The workspace pays for the Team plan.",
   "exit": "Becomes an evangelist, or churns back to the free plan and re-enters as a lead.",
   "owner": "[customer owner, an AE until CS exists]"
  },
  {
   "stage": "evangelist",
   "entry": "A paying customer refers another team or agrees to a case study.",
   "exit": "Stays while the account remains active.",
   "owner": "Marketing"
  }
 ],
 "mql_definition": "A free-plan account becomes an MQL when it shows fit (a work email domain and a team of 11 or more peopl
... (shortened here; the page shows it whole)

The page re-checks the reply; the API does not

The page holds every reply to account (recon.js): for an audit, the motion you chose; six areas in order, none called good under a high or medium flag and every high flag's area called bad, nothing claimed for an area the paste never states without a quote; every funnel rate, the weakest stage and the speed-to-lead equal to the page's arithmetic; every high and medium flag carried and a high one never downgraded; rebuild when two high flags stand or three areas are bad, and no "sound" while a high flag stands. For a spec, the lifecycle in the skill's order with Lead, MQL, SQL, Opportunity and Customer; an MQL definition needing fit and engagement; fit, engagement and negative scoring with signed points and a threshold no single download reaches; routing rules with a fallback; a first-contact SLA within 4 hours and a qualification decision within 48 hours, each with an escalation; reason codes; pipeline stages with exit criteria; MQL-to-SQL against 30-50%; the handed-off audit answered. In both, quotes are checked word for word and no count, rate, price or window may be invented beyond the skill's constants. Over the API you get the model's reply as it is; run the same checks yourself with Recon.reconcile in recon.js, or paste the same input into the page.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://revops-desk.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":"revops-desk"}' | 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 (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.

# input.json: the run input object - {"task": "audit", "process": "...", "context": "...", "facts": "..."}
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 revops-desk:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="revops-desk:audit:$(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 lane's contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream (spec)

The same run as Server-Sent Events. spec.json is a spec input, with audit_handoff when it follows an audit.

# 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: revops-desk:spec:$(shasum -a 256 spec.json | cut -c1-16):a1" --data @spec.json