← Carrier Desk Token page

Carrier Desk API

Everything the web app does, you can do from your own code: paste a carrier decision — a scorecard dump, an RFP bid table, a rate or fuel-surcharge proposal, an onboarding candidate, a compliance alert, a capacity-crunch email thread — and get it worked. The scenario named, the risk called, the single next move argued from the numbers on the page, and the traps flagged. Useful for sweeping a routing guide overnight, screening a bid round before the analyst sees it, or refusing to renew anything that comes back “Exit the carrier”. Base URL https://api.skillsafe.ai/v1/app-api.

Input fields

The object you send — and it is the input object directly, not wrapped in {"input": ...}. Only situation is required.

FieldTypeMeaning
situationstringRequired. The carrier record as pasted: scorecard tables, lane and volume lists, contracted rates and fuel-surcharge schedules, RFP bids, carrier or broker emails, FMCSA and insurance snapshots, claims notes, ops observations. Clipped at 40,000 characters — from the middle, keeping both ends, because a carrier worksheet carries its identifying header at the top and its live pressure (the proposal, the ops notes, the deadline) at the bottom. The cut is announced in-band with a bracketed marker saying how many characters were removed, and the model is instructed not to read across the gap.
contextstringOptional, clipped at 6,000 characters, also from the middle. Who is deciding, the portfolio and lanes at stake, service constraints, budget pressure, what decision is needed and what is off the table. It materially changes the answer: the same scorecard reads differently when lane A cannot lose service during peak.
factsstringOptional. The deterministic output of the app's browser-side calculator. Treated as a hint, not a fact — every figure is reconciled against situation before it is repeated, and where the two disagree the pasted material wins. May carry: the four scorecard metrics banded against the desk's targets and red flags with a weighted composite (and, when a whole column of periods was pasted rather than a single figure, the trend direction and a count of consecutive periods at or past the exit level); a fuel-surcharge total-cost model at $3.50, $4.00 and $4.50 diesel plus the user's own posted DOE price; a comparison of the current schedule against a proposed one across that range, with the diesel price at which the two cross and the annualized difference over weekly miles; contract-versus-benchmark and contract-versus-spot deltas; and a lane-concentration share against the 40% and 50% caps.
previousobjectOptional, present only on a re-run of the same carrier. Carries scenario, risk, action, confidence and when (a YYYY-MM-DD date) from the last assessment. The new material is assessed on its own merits; the reply names what changed and the evidence that moved it, and will not credit a change it cannot see on the page.
retry_notestringOptional, and not for humans. Tells the model its previous reply did not parse and to re-emit the same assessment in the required shape. It is a formatting instruction only: it can never change the scenario, the risk, the action or any number.

1. Get a token

Every call carries Authorization: Bearer <token>. Open the token page to sign in, reveal your token and copy a ready-made shell export. It never asks you to open the DevTools console. If you would rather script it, POST /guest mints a guest token for a named slug — that is where the app slug is bound, which is why no later path contains an /apps/{slug}/ segment. A guest token works for reading and estimating; a signed-in token is needed to run.

POST/guest

2. Check the session and balance

Confirms who the token belongs to and how many credits are available. Do this before a run: a 402 after submitting is avoidable.

GET/me

3. Estimate - free, no job created

Returns the credit hold a run would reserve, plus the resolved model and markup. It creates no job and charges nothing, so it is safe to call on every keystroke. The response carries model, model_alias, markup_bps, hold_credits, min_credits and sponsor_enabled.

POST/estimate
What this app resolves to. Carrier Desk requests the gpt-terra alias, which resolves to gpt-5.6-terra at markup_bps 1000. Read model and hold_credits from the estimate rather than assuming either — the alias is the stable part, the resolved model is not.

4. Run and poll

Creates a job and returns {"job_id": "..."} immediately; poll GET /jobs/{job_id} until status is terminal, then read data.output.output. Always send an Idempotency-Key: a retried request with the same key returns the original job instead of billing twice.

POST/run
GET/jobs/{job_id}

5. Run with streaming (SSE)

Same job, delivered as server-sent events. delta events carry incremental text, job carries the job id, and done carries the authoritative full output - trust done over the concatenated deltas, which can drop the tail. Send the same Idempotency-Key discipline here as on /run.

POST/run-stream

Output contract

data.output.output is plain text — no code fence around the response as a whole — in exactly this shape: five header lines, then six ## sections in this order. This is what the app's parser decodes; a reply that breaks any rule below is discarded and retried once.

SCENARIO: <Carrier onboarding | Scorecard review | RFP evaluation | Rate negotiation
           | Capacity crunch | Compliance alert | Financial distress | Insufficient information>
RISK: <Low | Moderate | High | Severe>
ACTION: <Award or expand volume | Onboard with trial | Renegotiate the rate
         | Rebalance the routing guide | Start corrective action | Sign with conditions
         | Exit the carrier | Investigate first | Insufficient information>
CONFIDENCE: <integer 0-100>
SUMMARY: <2 to 4 sentences>

## Next moves
- <move, argued from a number on the page>

## The numbers
- <figure, with where it came from>

## Scorecard read
- <metric: value, band, direction>

## Risk exposure
- <exposure and what it costs>

## Watch items
- <what to re-check, and when>

## Open questions
- <question>
Parsing rules, as implemented. SCENARIO: is the first line and must be exactly one of the eight values. RISK: is one of Low, Moderate, High, Severe. ACTION: is exactly one of the nine values. CONFIDENCE: is a bare integer 0-100, no percent sign. SUMMARY: is 2 to 4 sentences, may wrap, and ends at the first blank line. All six ## headings must appear, spelled exactly, in that order. Every line inside a section is a - bullet, which may wrap onto indented continuation lines. An empty section carries the single bullet - None.

The eight scenarios and the nine actions

SCENARIO is one of: Carrier onboarding, Scorecard review, RFP evaluation, Rate negotiation, Capacity crunch, Compliance alert, Financial distress, Insufficient information. ACTION is one of: Award or expand volume, Onboard with trial, Renegotiate the rate, Rebalance the routing guide, Start corrective action, Sign with conditions, Exit the carrier, Investigate first, Insufficient information. There is exactly one action — the single next move — not a menu.

Sections that may not be empty

Only Scorecard read and Open questions may legitimately come back as - None. Two rules are enforced on top of that: if RISK is High or Severe, Risk exposure can never be - None.; and if ACTION is Insufficient information, Open questions can never be - None. A reply that breaks the shape is discarded and the app retries once with the shape spelled out, reusing the same idempotency family so the retry cannot double-bill.

The constants the answer is argued against

The output is checkable because it is argued against fixed desk constants rather than sentiment. The four scorecard metrics carry a target and a red flag: on-time delivery targets 95% and red-flags at 90%; tender acceptance targets 90% and red-flags at 80%; the claims ratio, as a percent of spend, targets 0.5% and red-flags at 1.0%; invoice accuracy targets 97% and red-flags at 93%. The composite weights them on-time delivery 35, acceptance 25, claims 20 and invoice accuracy 20. The exit thresholds are duration-qualified, so a single bad period is never an exit: on-time delivery below 85% for 60 days, tender acceptance below 70% for 30 days with no communication, a claims ratio above 2% for 90 days, or invoice accuracy below 88% for 90 days after notice. Lane concentration is capped at 40% on a critical lane, with 50% the level that trips escalation. Where the reply cites one of these, you can check it yourself against the same pasted numbers.

Re-runs and reformat retries

previous and retry_note ride along in the same input object. They are the only two fields you would not send on a first pass:

{
  "situation": "CARRIER: Redhawk Transit Lines Inc (asset, MC-742619) ...",
  "previous": {
    "scenario": "Scorecard review",
    "risk": "High",
    "action": "Start corrective action",
    "confidence": 72,
    "when": "2026-06-18"
  },
  "retry_note": "Your previous reply did not parse. Re-emit the SAME assessment, unchanged in substance, in the required shape: five header lines then the six ## sections in order, every section line a '- ' bullet."
}
Grounding. The assessment only argues from what is on the page. A figure in facts that cannot be reconciled against situation is dropped rather than repeated, and a change since previous that has no evidence in the new material is not credited. Where the record does not support a call, the scenario and the action are both Insufficient information and the questions that would settle it go in Open questions.

The envelope and error codes

Every response is {"ok": true, "data": {...}} or {"ok": false, "error": {"code": "...", "message": "..."}}. Check ok before reading data.

StatusCodeWhat to do
400VALIDATION_ERRORThe input shape is wrong. error.details names the field — most often a missing situation, or an input object wrapped in {"input": ...} when it should be sent directly.
401UNAUTHORIZEDMissing, malformed or expired token. Mint a new one from the token page or POST /guest.
402PAYMENT_REQUIREDThe balance is below the run's hold. Call /estimate first and compare against /me.
404NOT_FOUNDWrong slug or job id.
429RATE_LIMITEDBack off and retry with the same idempotency key.
5xxINTERNALRetry with the same idempotency key; a completed job is returned rather than re-billed.
Idempotency. Send Idempotency-Key on every /run and /run-stream. Derive it from a hash of the input plus an attempt counter, so a network retry collapses server-side while a genuine re-run of the same carrier gets its own key. The app does exactly this, including on its automatic reformat retry, which stays inside the same idempotency family.