Drive After the Fees from your own code
The one metered task, price, is available over HTTP. It takes one handmade listing - its price, the shipping you charge, what postage and packaging cost you, materials, making time, a coupon, your offsite ads setting, a target margin, competitor prices and a line on what makes it worth it - and returns a pricing read: a suggested list price with a verdict (raise, hold, lower or rethink), why a buyer pays it, the largest safe coupon, free or charged shipping, what offsite ads mean for this listing, three to five steps and what to watch afterwards.
The division of labour first: the page counts; the model judges. The page works out the whole fee stack - listing, transaction, payment processing and offsite ads fees - the take-home, the profit after your costs, the break-even and target-margin prices, a coupon grid and the competitor spread, and sends them as facts. The model must not recompute them differently. After the reply, the page recomputes the suggested price and coupon with the same sums and lists anything that disagrees.
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"after-the-fees"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required field (task, item, currency, price, shipping_charged, shipping_cost, materials, labour_minutes, hourly_rate, coupon_percent, offsite_ads, offsite_share, target_margin, competitor_prices, notes, fee_rates, facts) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: the pricing read is metered and needs a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field, the input and the rules
Every run sends task: price (the one task is price); a missing or different one is still answered as price. All fields are strings.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always price. |
item | yes | The listing, in your words (up to 80 characters). |
currency | yes | USD, GBP, EUR, CAD or AUD. |
price | yes | The current list price, before any coupon. |
shipping_charged | yes | What the buyer pays for shipping; 0 means free shipping. |
shipping_cost | yes | What postage and packaging cost you. |
materials | yes | Materials per item. |
labour_minutes | yes | Making time per item; 0 leaves your time out. |
hourly_rate | yes | What you want per hour of making. |
coupon_percent | yes | The coupon or sale you run or plan; 0 for none. |
offsite_ads | yes | off, 15% or 12%. |
offsite_share | yes | Your guess at the percent of sales that come through offsite ads. |
target_margin | yes | The margin you want, as a percent of what the buyer pays. |
competitor_prices | yes | Comparable listings' prices, comma-separated, or none (the page keeps up to 40). |
notes | yes | What makes it worth it, or none (up to 1,200 characters). |
fee_rates | yes | The fee rates the page used, as one line. |
facts | yes | The page's sums, one line each: the direct sale, the offsite-ad sale, the expected profit, the band, the break-even and target prices, the biggest safe coupon and the competitor spread. Build it with FeeKit.buildInput. |
retry_note | no | Only on a reformat retry: why the previous reply was unusable. |
The sums: the buyer pays the list price less the coupon plus the shipping charged. Transaction (6.5% by default), payment processing (3% + 0.25) and offsite ads (15% or 12%, capped per order) fees are charged on that total; the listing fee (0.20) once per sale. Profit is what is left after every fee, materials, postage and packaging and your time. Sales tax is left out.
The rules the reply follows: the suggested price is at or above the break-even list price; the verdict is raise when it is more than 2% above your price, lower when more than 2% below, otherwise hold, or rethink when no price near the competitor range reaches your target margin or the costs need to change; the coupon is one of 0, 10, 15, 20, 25, 30, 40 or 50% and must not make a direct sale at the suggested price lose money.
The reply
One JSON object, no prose around it:
{"task": "price", "headline": "...", "verdict": "raise", "price": 44.00, "price_why": "...", "value": ["...", "..."], "coupon": {"max_percent": 15, "note": "..."}, "shipping": {"choice": "charge", "note": "..."}, "offsite": {"note": "..."}, "competitors": "...", "steps": [{"do": "...", "why": "..."}], "watch": ["...", "..."]}
| Field | What it holds |
|---|---|
headline | One sentence: what the listing keeps now and the move suggested. |
verdict | raise, hold, lower or rethink. |
price | A number: the suggested list price, before any coupon, in your currency. |
price_why | Two or three sentences tying the price to the facts. |
value[] | Two to four reasons a buyer pays the price, drawn only from your notes. |
coupon | max_percent (a grid step) and a note. |
shipping | choice (free or charge) and a note. |
offsite | A note on what offsite ads do to this listing. |
competitors | Where the suggested price sits against the competitor spread, or that none were given. |
steps[] | Three to five {do, why}, most important first. |
watch[] | Two to four things to watch after the change. |
The page reads the reply with ReplyKit.normalize and checks it with ReplyKit.check, which recomputes a direct sale at the suggested price and lists, under "The page's check", a price under break-even, a verdict that does not match the price, an offsite-ad sale that still loses money, a price under the cheapest competitor, a coupon off the grid or one that loses money, a missing shipping choice, and a competitor amount quoted when none were given.
Worked examples - every verdict
These are the free samples on the page: the exact input the page sends and the reply that replays for free. Together they cover every verdict: raise, hold, rethink and lower.
A ceramic mug - raise the price, to $49.50 (now: under your target)
Input:
{
"task": "price",
"item": "Hand-thrown ceramic mug, speckled glaze, 12 oz",
"currency": "USD",
"price": "32",
"shipping_charged": "6",
"shipping_cost": "7.50",
"materials": "6",
"labour_minutes": "45",
"hourly_rate": "20",
"coupon_percent": "15",
"offsite_ads": "15%",
"offsite_share": "10",
"target_margin": "30",
"competitor_prices": "38.00, 42.00, 45.00, 48.00, 52.00, 55.00",
"notes": "Thrown on the wheel and glazed by hand, each one slightly different. Dishwasher safe. Buyers often order two as a gift set and mention the glaze in reviews.",
"fee_rates": "listing 0.20, transaction 6.5%, processing 3% + 0.25, offsite ads cap 100",
"facts": "Direct sale at $32.00 with a 15% coupon: buyer pays $33.20, fees $3.60 (listing $0.20, transaction $2.16, processing $1.25), take-home $29.60, costs $28.50 (materials $6.00, postage and packaging $7.50, time $15.00), profit $1.10 = 3.3% margin.\nSame sale through an offsite ad: ads fee $4.98, profit -$3.88 = -11.7%.\nExpected profit per sale with 10% of sales through offsite ads: $0.60.\nBand: Under your target (target margin 30.0%).\nBreak-even list price (with that coupon applied): $30.58 ($38.06 on an offsite-ad sale). Target-margin list price: $49.24 ($67.80 on an offsite-ad sale).\nBiggest coupon on the grid (0,10,15,20,25,30,40,50%) that keeps a direct sale at or above zero profit at the current price: 15%; on an offsite-ad sale: none; at or above the target margin: none.\nCompetitor prices (6): low $38.00, lower quarter $42.75, median $46.50, upper quarter $51.00, high $55.00; your price sits in the low of the range (0 of 6 cheaper).\nRethink trigger (target-margin list price above every competitor price): no."
}
Reply:
{
"task": "price",
"headline": "At $32.00 this mug nets only $1.10 a sale (3.3% margin) - raising to $49.50 clears both break-evens, lands near the competitor median, and gets you to your 30% target margin.",
"verdict": "raise",
"price": 49.5,
"price_why": "Facts put the direct break-even at $30.58 and the offsite-ad break-even at $38.06, with a target-margin price of $49.24. $49.50 clears both and sits just a little above the target-margin price. That also places you inside the $38.00-$55.00 competitor spread instead of below every competitor, where your current $32.00 sits.",
"value": [
"Hand-thrown and hand-glazed, so every mug is slightly different",
"Dishwasher safe for everyday use",
"A favorite gift-set pick - buyers often order two at once",
"Buyers call out the glaze in their own reviews"
],
"coupon": {
"max_percent": 15,
"note": "15% is the biggest step on your grid that still clears a 30% margin at $49.50 - it nets about $14.56 profit (30.3%) on a direct sale. Even a sale through an offsite ad stays profitable at that price and coupon, about $7.35 (15.3%), unlike today."
},
"shipping": {
"choice": "free",
"note": "Folding your $6.00 shipping charge in brings the item price to $55.50, just above the current competitor high of $55.00. In USD, that keeps every order at $35.00 or more, which earns Etsy's free-shipping search priority."
},
"offsite": {
"note": "Offsite ads take 15% of what the buyer pays and turn today's $32.00 sale into a $3.88 loss; at $49.50 an offsite-ad sale stays profitable instead, about $7.35 (15.3%) with the 15% coupon or $12.95 (23.3%) without one. With roughly 10% of sales arriving through offsite ads, that turns a drag on profit into a small net gain instead of today's thin $0.60 blended average."
},
"competitors": "Your suggested $49.50 sits between the competitor median ($46.50) and upper quartile ($51.00), solidly inside the $38.00-$55.00 spread rather than at the cheap end where your current $32.00 sits.",
"steps": [
{
"do": "Raise the price in two steps rather than all at once - for example to about $40.00 first, then up to $49.50 a few weeks later.",
"why": "The move is more than 25% above your current $32.00; facts show the target-margin price is $49.24, so staging it avoids a sudden jump for repeat buyers."
},
{
"do": "Switch to free shipping and fold the $6.00 charge into the list price.",
"why": "At $55.50 landed, every order clears Etsy's $35.00 free-shipping search-priority threshold, and fees are the same either way."
},
{
"do": "Keep the coupon at 15% and no higher.",
"why": "Facts show a bigger coupon already loses money on an offsite-ad sale at the old price; at $49.50, 15% is the largest step that still clears your 30% target margin."
},
{
"do": "Call out the hand-glazing and the gift-set habit on the listing page.",
"why": "Notes show buyers already order two as gifts and mention the glaze in reviews - worth stating up front to support the new price."
}
],
"watch": [
"Favourites-to-sales ratio in the weeks after each price step",
"Conversion rate right after each increase",
"15% coupon redemption rate at the new price",
"Share of sales coming through offsite ads versus your 10% estimate"
]
}
Clay earrings - hold the price, to $28.00 (now: on target)
Input:
{
"task": "price",
"item": "Polymer clay statement earrings, arch shape",
"currency": "USD",
"price": "28",
"shipping_charged": "0",
"shipping_cost": "4.50",
"materials": "2.50",
"labour_minutes": "30",
"hourly_rate": "18",
"coupon_percent": "0",
"offsite_ads": "off",
"offsite_share": "0",
"target_margin": "30",
"competitor_prices": "18.00, 22.00, 25.00, 30.00, 35.00, 26.00",
"notes": "Lightweight, hypoallergenic hooks, sent in a recycled gift box. Free shipping already. Repeat buyers collect colourways.",
"fee_rates": "listing 0.20, transaction 6.5%, processing 3% + 0.25, offsite ads cap 100",
"facts": "Direct sale at $28.00: buyer pays $28.00, fees $3.11 (listing $0.20, transaction $1.82, processing $1.09), take-home $24.89, costs $16.00 (materials $2.50, postage and packaging $4.50, time $9.00), profit $8.89 = 31.8% margin.\nOffsite ads: opted out, so no ads fee.\nBand: On target (target margin 30.0%).\nBreak-even list price: $18.18. Target-margin list price: $27.20.\nBiggest coupon on the grid (0,10,15,20,25,30,40,50%) that keeps a direct sale at or above zero profit at the current price: 30%; at or above the target margin: 0%.\nCompetitor prices (6): low $18.00, lower quarter $22.75, median $25.50, upper quarter $29.00, high $35.00; your price sits in the middle of the range (4 of 6 cheaper).\nRethink trigger (target-margin list price above every competitor price): no."
}
Reply:
{
"task": "price",
"headline": "At $28.00 you keep a 31.8% margin on this listing, already on target, so hold the price.",
"verdict": "hold",
"price": 28,
"price_why": "Your current $28.00 is already above the $27.20 target-margin price and well clear of the $18.18 break-even, giving a 31.8% margin against your 30% target. It sits mid-range against competitors ($18.00-$35.00, median $25.50), with 4 of 6 priced lower, so there's no reason in your notes to move it.",
"value": [
"Lightweight earrings with hypoallergenic hooks, so sensitive ears can wear them",
"Arrives in a recycled gift box",
"Free shipping already included",
"Repeat buyers come back to collect new colourways"
],
"coupon": {
"max_percent": 20,
"note": "At $28.00, a 20% coupon still clears about 17% margin, profitable but under your 30% target; a 25% coupon would drop margin to about 12%, below half your target, so keep coupons at 20% or less."
},
"shipping": {
"choice": "free",
"note": "You already charge $0 for shipping, so it's folded into the $28.00 price; that's below the $35 order threshold for Etsy's free-shipping search boost, but that bonus is order-level, not a reason to raise this item's price."
},
"offsite": {
"note": "You're opted out of offsite ads, so none of your sales carry that extra fee right now; if you turn ads on, any sale through that channel would pay an ads fee on top of your usual fees, trimming margin on just those sales."
},
"competitors": "Your $28.00 sits in the middle of the competitor spread ($18.00-$35.00), above the $25.50 median and just under the $29.00 upper quarter, with 4 of 6 competitors priced lower.",
"steps": [
{
"do": "Hold the list price at $28.00",
"why": "It already returns a 31.8% margin against your 30% target and sits above the $27.20 target-margin price."
},
{
"do": "Cap any coupon at 20% off",
"why": "20% off still keeps about 17% margin; the next step up, 25%, falls below half your target margin."
},
{
"do": "Call out the hypoallergenic hooks and recycled gift box in your listing photos or description",
"why": "These are the differentiators in your notes and support a price above the $25.50 competitor median."
},
{
"do": "Keep shipping free and folded into the price as now",
"why": "Fees are charged on item plus shipping either way, so this doesn't cost you anything and buyers already see $0 at checkout."
},
{
"do": "Invite repeat colourway buyers onto a mailing list or favourites alert",
"why": "Your notes say repeat buyers collect colourways, so a heads-up on new releases can turn that into more direct sales."
}
],
"watch": [
"Favourites-to-sales ratio over the next few weeks",
"Conversion rate if you ever test a higher price",
"How often buyers redeem a 20% coupon",
"Share of sales coming from repeat colourway buyers"
]
}
A small art print - rethink the costs, to £12.00 (now: under your target)
Input:
{
"task": "price",
"item": "A5 botanical art print, unframed",
"currency": "GBP",
"price": "12",
"shipping_charged": "3.50",
"shipping_cost": "4.20",
"materials": "2.80",
"labour_minutes": "10",
"hourly_rate": "15",
"coupon_percent": "20",
"offsite_ads": "12%",
"offsite_share": "25",
"target_margin": "40",
"competitor_prices": "8.00, 9.00, 10.00, 11.00, 12.00",
"notes": "Printed at home on 300gsm card from my own watercolour. Sent flat in a board-backed envelope.",
"fee_rates": "listing 0.16, transaction 6.5%, processing 4% + 0.20, offsite ads cap 80",
"facts": "Direct sale at £12.00 with a 20% coupon: buyer pays £13.10, fees £1.74 (listing £0.16, transaction £0.85, processing £0.72), take-home £11.36, costs £9.50 (materials £2.80, postage and packaging £4.20, time £2.50), profit £1.86 = 14.2% margin.\nSame sale through an offsite ad: ads fee £1.57, profit £0.29 = 2.2%.\nExpected profit per sale with 25% of sales through offsite ads: £1.47.\nBand: Under your target (target margin 40.0%).\nBreak-even list price (with that coupon applied): £9.40 (£11.53 on an offsite-ad sale). Target-margin list price: £20.53 (£28.50 on an offsite-ad sale).\nBiggest coupon on the grid (0,10,15,20,25,30,40,50%) that keeps a direct sale at or above zero profit at the current price: 30%; on an offsite-ad sale: 20%; at or above the target margin: none.\nCompetitor prices (5): low £8.00, lower quarter £9.00, median £10.00, upper quarter £11.00, high £12.00; your price sits in the high of the range (4 of 5 cheaper).\nRethink trigger (target-margin list price above every competitor price): yes."
}
Reply:
{
"task": "price",
"headline": "This A5 print holds £12.00, already at the top of the competitor range, but no price inside that range gets you to a 40% margin - the fix is costs, not price.",
"verdict": "rethink",
"price": 12,
"price_why": "£12.00 already sits at the top of the competitor spread (tied with the highest of five listings, four of them cheaper) and clears the £9.40 break-even comfortably. The £20.53 list price you'd need for a 40% margin is above every competitor you gave, so there is no price in this market that reaches your target - that is why this needs a rethink rather than a price move.",
"value": [
"An original watercolour design, not a mass-produced print",
"Printed on sturdy 300gsm card",
"Sent flat and protected in a board-backed envelope"
],
"coupon": {
"max_percent": 10,
"note": "At £12.00 a 10% coupon keeps a direct sale's margin near 20% (half your 40% target), against about 14.2% at the current 20% coupon. A sale through an offsite ad at 10% off still turns a small profit, so it is not a loss."
},
"shipping": {
"choice": "charge",
"note": "Keep the £3.50 shipping charge separate - folding it in would push the item to £15.50, above every competitor you listed. Worth noting your actual postage and packaging cost £4.20, so the current charge already runs £0.70 short."
},
"offsite": {
"note": "You clear the £11.53 offsite-ad break-even at £12.00, but a sale made through an offsite ad still nets only about £0.29 (2.2%) against £1.86 (14.2%) direct, and with a quarter of your sales arriving that way your expected profit per sale is about £1.47."
},
"competitors": "At £12.00 you are tied with the most expensive of the five listings you gave (£8.00-£12.00), with four of them cheaper - there is no room to price higher and stay inside that spread.",
"steps": [
{
"do": "Close the shipping gap first",
"why": "You charge £3.50 for shipping but postage and packaging cost £4.20, a £0.70 shortfall on every sale before fees or coupons."
},
{
"do": "Review materials and packaging costs",
"why": "Materials (£2.80) and postage (£4.20) make up most of your £9.50 cost base, and the £20.53 target-margin price is unreachable against these competitors, so the gap has to close on the cost side."
},
{
"do": "Cut the running coupon from 20% to 10%",
"why": "At £12.00, a 10% coupon keeps margin near 20% versus about 14.2% at the current 20% coupon."
},
{
"do": "Hold the list price at £12.00 for now",
"why": "You are already at the top of the £8.00-£12.00 competitor spread and above the £9.40 break-even; there is no room to raise it further inside that market."
}
],
"watch": [
"Favourites-to-sales conversion after moving the coupon to 10%",
"Whether offsite-ad sales stay near a quarter of the total",
"Postage cost changes, since £4.20 already exceeds the £3.50 you charge",
"Conversion at the unchanged £12.00 price"
]
}
A soy candle - lower the price, to $31.50 (now: on target)
Input:
{
"task": "price",
"item": "Soy wax candle in amber glass, 8 oz, fig and cedar",
"currency": "USD",
"price": "48",
"shipping_charged": "7",
"shipping_cost": "8",
"materials": "7.50",
"labour_minutes": "20",
"hourly_rate": "20",
"coupon_percent": "0",
"offsite_ads": "15%",
"offsite_share": "15",
"target_margin": "30",
"competitor_prices": "24.00, 26.00, 28.00, 30.00, 32.00, 34.00",
"notes": "Hand-poured in small batches, cotton wick, about 45 hours of burn. Same jar and wax as most candle shops.",
"fee_rates": "listing 0.20, transaction 6.5%, processing 3% + 0.25, offsite ads cap 100",
"facts": "Direct sale at $48.00: buyer pays $55.00, fees $5.68 (listing $0.20, transaction $3.58, processing $1.90), take-home $49.33, costs $22.17 (materials $7.50, postage and packaging $8.00, time $6.67), profit $27.16 = 49.4% margin.\nSame sale through an offsite ad: ads fee $8.25, profit $18.91 = 34.4%.\nExpected profit per sale with 15% of sales through offsite ads: $25.92.\nBand: On target (target margin 30.0%).\nBreak-even list price: $18.00 ($22.96 on an offsite-ad sale). Target-margin list price: $30.39 ($42.71 on an offsite-ad sale).\nBiggest coupon on the grid (0,10,15,20,25,30,40,50%) that keeps a direct sale at or above zero profit at the current price: 50%; on an offsite-ad sale: 50%; at or above the target margin: 30%.\nCompetitor prices (6): low $24.00, lower quarter $26.50, median $29.00, upper quarter $31.50, high $34.00; your price sits in the high of the range (6 of 6 cheaper).\nRethink trigger (target-margin list price above every competitor price): no."
}
Reply:
{
"task": "price",
"headline": "At $48.00 this candle is priced above every competitor even though it only needs to clear $18.00 to break even, so bring it down to $31.50, right at the competitor upper quartile.",
"verdict": "lower",
"price": 31.5,
"price_why": "Break-even is $18.00 and the target-margin price is $30.39, but the current $48.00 sits above all six competitors (high $34.00). $31.50 sits a little above the target-margin price and lands exactly at the competitor upper quartile, comfortably clear of both the direct and the $22.96 offsite-ad break-even.",
"value": [
"Hand-poured in small batches",
"Cotton wick with about 45 hours of burn time",
"Amber glass jar in a fig and cedar scent, 8 oz"
],
"coupon": {
"max_percent": 25,
"note": "25% off $31.50 still keeps a direct sale at about 17% margin, above half your 30% target; a 30% coupon drops below that. On an offsite-ad sale, 25% off leaves only pennies of profit, so save it for direct sales."
},
"shipping": {
"choice": "free",
"note": "Folding your $7.00 shipping into the price makes it $38.50, clearing Etsy's $35.00 free-shipping threshold for search priority; fees are the same either way."
},
"offsite": {
"note": "Offsite ads take 15% of what the buyer pays on top of your usual fees. Even at $31.50 an offsite-ad sale still clears the $22.96 break-even, but margin falls to roughly 17% versus about 32% on a direct sale, so a heavier mix of ad-driven sales will cut into profit."
},
"competitors": "At $31.50 you sit right at the competitor upper quartile, above the $29.00 median and below the $34.00 high, solidly inside the $24.00 to $34.00 spread.",
"steps": [
{
"do": "Lower the list price from $48.00 to $31.50.",
"why": "The current price sits above all six competitors, while $31.50 clears both the $18.00 break-even and the $30.39 target-margin price."
},
{
"do": "Cut the price in two steps (for example $48.00 to $40.00, then to $31.50) rather than all at once.",
"why": "The move is more than 25% down, so a staged cut lets you watch favourites and sales before the full drop."
},
{
"do": "Switch to free shipping advertised at $38.50 (item plus the current $7.00 postage).",
"why": "It clears the $35.00 US free-shipping threshold for search priority without changing your fees."
},
{
"do": "Cap any coupon at 25% off the new $31.50 price.",
"why": "A 30% coupon would drop direct-sale margin below half your 30% target."
}
],
"watch": [
"Favourites-to-sales ratio after the price drop",
"Conversion rate in the weeks after each step",
"Coupon redemption rate at 25%",
"Share of sales coming through offsite ads"
]
}
Build the input yourself
Load the engine in node (global.window = global; require("./feekit.js"); require("./replykit.js")), then:
const K = FeeKit, R = ReplyKit;
const built = K.buildInput(K.form({ item: "Hand-thrown ceramic mug", currency: "USD", price: "32", ship_charged: "6",
ship_cost: "7.50", materials: "6", minutes: "45", rate: "20", coupon: "15", ads: "15", ads_share: "10", target: "30",
comps: "28, 35, 40, 24, 45", notes: "Thrown by hand, each one different." })); // built.ok is false, with built.note, when a number is missing
const wire = K.toWire(built.input); // POST this to /estimate and /run; built.facts holds every sum
// then: const reply = R.normalize(R.parseReply(job.output.output));
// R.check(reply, built).notes // the page's re-check of the reply
// R.md(built, reply, K.title(built.form)) // the receipt, grid and read as Markdown
Form keys: item, currency, price, ship_charged, ship_cost, materials, minutes, rate, coupon, ads (15, 12 or off), ads_share, target, comps, notes and fees (listing, txn, proc_pct, proc_fixed, ads_cap).
Step 1 — a tiny client helper
One function that sends JSON and unwraps the envelope. 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="paste-your-token-here" # from https://after-the-fees.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"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://after-the-fees.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// after-the-fees.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://after-the-fees.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://after-the-fees.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// AfterTheFees.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class AfterTheFees {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://after-the-fees.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://after-the-fees.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://after-the-fees.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://after-the-fees.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
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":"after-the-fees"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "after-the-fees"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "after-the-fees" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "after-the-fees"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "after-the-fees"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "after-the-fees" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "after-the-fees"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "after-the-fees" }))!["token"]!.GetValue<string>();
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}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["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": "price", "item": "Hand-thrown ceramic mug", "currency": "USD", "price": "32", "materials": "6", "shipping_cost": "7.50", "...": "...", "facts": "...", ...}
# - see the field table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "price", "item": "Hand-thrown ceramic mug", "currency": "USD", "price": "32", "materials": "6", "shipping_cost": "7.50", "...": "...", "facts": "...", ...}
# - see the field table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "price", item, currency, price, materials, ..., facts} (see the field table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
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 after-the-fees:<task>:<inputHash>:a<attempt> (the one task is price), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="after-the-fees:price:$(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}'
import hashlib, time
key = "after-the-fees:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict"), reply.get("price"), reply.get("coupon"))
import { createHash } from "node:crypto";
const key = `after-the-fees:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.verdict, reply.price, reply.coupon);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "after-the-fees:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
fmt.Println(j["charged_credits"], j["truncated"], j["output"].(map[string]any)["output"])
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "after-the-fees:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply);
require "digest"
key = "after-the-fees:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["verdict"], reply["price"], reply["coupon"]].inspect
$key = "after-the-fees:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", ($reply["verdict"] ?? ""), " ", ($reply["price"] ?? ""), "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"after-the-fees:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply}");
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: after-the-fees:price:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "price", "item": "Hand-thrown ceramic mug", "currency": "USD", "price": "32", "materials": "6", "shipping_cost": "7.50", "...": "...", "facts": "...", ...}
# - see the field table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "after-the-fees:price:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `after-the-fees:price:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "after-the-fees:price:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "after-the-fees:price:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "after-the-fees:price:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: after-the-fees:price:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"after-the-fees:price:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);