api.hunky.dev/v1

Hunky Cloud API

Two planes, one host. The management plane (sign up, keys, plan) is what you do as the account owner; the use plane (/v1/pretty) is the product — staging hunks. Each example below is shown three ways where it applies: curl, the hunky-cli, and the dashboard.

Introduction

Everything lives under https://api.hunky.dev/v1. The two planes use different credentials, on purpose (least privilege):

  • Management — a short-lived bearer token from /v1/login. Manages keys, plan, billing.
  • Use — an API key (hk_live_…). Stages hunks via /v1/pretty, and nothing else.

A key cannot reach the management plane and a token cannot reach the pretty API — so a leaked staging key can't change your billing. Anything that speaks HTTP can drive it: a shell, CI, or an agent.

Authentication

Sign up or log in to get a management token (valid 1 hour). Pass it as Authorization: Bearer <token> on management calls.

Sign up

curl -s https://api.hunky.dev/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"you@company.com","password":"correct-horse","name":"You"}'
# → 201 { "token": "…", "account_id": "…", "expires_in": 3600 }

Log in

curl -s https://api.hunky.dev/v1/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@company.com","password":"correct-horse"}'
# → 200 { "token": "…", "expires_in": 3600 }

Account

Your plan, both usage pools — credit_cents (promo, starts at $5) and balance_cents (real, topped up) — usage count, and the list of API keys (hashes never returned). Usage drains promo first, then balance, which may run $5 negative before /v1/pretty returns 402 until you top up.

curl -s https://api.hunky.dev/v1/account \
  -H "Authorization: Bearer $TOKEN"
# → { name, plan,
#     credit_cents,   // promo credit (starts at $5)
#     balance_cents,  // real balance, recharged via top-up
#     stages_count,
#     keys: [{ prefix, name, env, created_at }] }

API keys

Keys are the use-plane credential. Mint them from the management plane; the raw key is shown once and only its hash is stored.

Create a key

curl -s -X POST https://api.hunky.dev/v1/keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci","env":"prod"}'
# → 201 { "api_key": "hk_live_…", "prefix": "hk_live_…", "name": "ci", "env": "prod" }
# the raw api_key is shown ONCE — store it now.

List keys

curl -s https://api.hunky.dev/v1/account \
  -H "Authorization: Bearer $TOKEN" | jq '.keys'
# keys live on the account object (hashes are never returned)

Revoke a key

curl -s -X DELETE https://api.hunky.dev/v1/keys/hk_live_8f3a \
  -H "Authorization: Bearer $TOKEN"
# → 204

Plans

One of pretty_boy, mamas_boy, or whole_package — or none to drop the subscription and run free / pay-as-you-go. They're tip jars with benefits — each unlocks more Brawny review modes and a little usage — and are billed separately from your usage balance.

curl -s -X POST https://api.hunky.dev/v1/plan \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan":"whole_package"}'
# → 200 { "plan": "whole_package" }
# Drop the subscription (free / PAYG-only):
#   -d '{"plan":"none"}'
A note on payment methods. Canceling (none) and switching between paid tiers work straight from the API/CLI once a card is on file — your request hits our server, which drives Stripe; the card never touches the CLI. Starting a paid plan for the first time needs a card, so that one call returns a one-time Stripe checkout link to finish in the browser. After that, every change is instant from the CLI.

The diff payload

The pretty API's diff field is a unified diff, gzip-compressed and base64-encoded — the only data that leaves your machine. The CLI builds it for you; with curl, build it yourself:

git diff HEAD -- feature.go | gzip | base64 | tr -d '\n'

Staging — the handshake

A request is two calls. POST /v1/pretty returns a request_key (202); GET /v1/pretty/<key> redeems it — 202 while warming, 200 with the result. The server never touches your tree; for staging it hands back a git apply --cached you run locally.

# 1. base64-gzip the diff, on one line.
DIFF=$(git diff HEAD -- feature.go | gzip | base64 | tr -d '\n')

# 2. submit → a request key
curl -s -X POST https://api.hunky.dev/v1/pretty \
  -H "Authorization: Bearer $HUNKY_CLOUD_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"mode\":\"stage\",\"file\":\"feature.go\",\"hunks\":[1],\"diff\":\"$DIFF\"}"
# → 202 { "request_key": "req_…", "poll_after_ms": 600 }

# 3. redeem → 202 while warming, 200 with { commands, review, meta }
curl -s https://api.hunky.dev/v1/pretty/req_… \
  -H "Authorization: Bearer $HUNKY_CLOUD_KEY"
# then run each string in .commands locally (a git apply --cached).

Operations

Each operation is the same handshake with a different request body. The curl tab shows the body; the CLI tab shows the one-liner.

List hunks

Number the hunks so you can choose which to stage.

{ "mode": "list", "file": "feature.go", "diff": "<base64-gzip>" }

Count hunks

Return just the number of hunks.

{ "mode": "count", "file": "feature.go", "diff": "<base64-gzip>" }

Stage by number

The result's commands[] is what you run locally.

{ "mode": "stage", "file": "feature.go", "hunks": [1, 3, 5], "diff": "<base64-gzip>" }

Stage everything except

Invert the selection.

{ "mode": "stage", "file": "feature.go", "hunks": [4], "invert": true, "diff": "<base64-gzip>" }

Stage by line range

Whole hunks overlapping a range of file lines.

{ "mode": "stage", "file": "feature.go", "lines": "455-480", "diff": "<base64-gzip>" }

Split a hunk

Only the +/- lines within a range.

{ "mode": "stage", "file": "CHANGELOG.md", "pick_lines": "25-37", "diff": "<base64-gzip>" }

Dry run

Return the patch without staging. commands[] is empty.

{ "mode": "dryrun", "file": "feature.go", "hunks": [4], "diff": "<base64-gzip>" }

The result object

Returned by GET /v1/pretty/<key> with a 200. Run each string in commands locally, in order.

FieldTypeDescription
outputstringText to print: the hunk list, the count, or the dry-run patch.
commandsstring[]Shell commands to run locally to stage (a git apply --cached). Empty for read-only modes.
reviewstringBrawny-1's unsolicited code review.
metaobject{ gpu, model, millis } — operational metadata.
{
  "output": "",
  "commands": [
    "git apply --cached <<'HUNKYPATCH'\n…patch…\nHUNKYPATCH"
  ],
  "review": "Hey there, pretty mama. Clean as I part my hair. Ship it.",
  "meta": { "gpu": "1080 Ti", "model": "Brawny-1", "millis": 4217 }
}

Request parameters (POST /v1/pretty)

FieldTypeDescription
modestring"list", "count", "dryrun", or "stage". Defaults to "stage".
filestringPath of the file within the diff to operate on. Required.
diffstringbase64(gzip(unified diff)). The only data that leaves your machine. Required.
hunksinteger[]1-based hunk numbers, for "stage" and "dryrun".
invertbooleanStage every hunk EXCEPT those in hunks. Default false.
linesstring"START-END" — stage whole hunks overlapping the line range.
pick_linesstring"A-B" — stage only the +/- lines within the range (splits a hunk).
review_modestringBrawny review style: standard | terse | verbose | haiku | sonnet | mythos. Each is unlocked by your plan; locked modes fall back to standard. mythos returns the empty string (national security).

Errors & status codes

StatusMeaning
400The request body or the diff payload could not be parsed.
401Missing or invalid credential (management token or API key).
404Unknown request key on GET (or it expired).
422The diff was valid but hunky could not operate on it (e.g. no such hunk).
202On GET: the result is not ready yet. Wait poll_after_ms and retry.
The same staging runs instantly, offline, and free with the local hunky binary. The API exists for hosts that can't install a binary — or agents that only speak HTTP.

hunky

Stage just the hunks you want, no TUI. The one git operation a non-interactive caller can't do — now it can. Man, those are some pretty hunks.

hunky is a real, free, MIT-licensed tool. Hunky Cloud is a parody (see its disclaimer for the, ah, fine print). Brawny Johvo is a Legally Distinct Non-Infringing™ original character and any resemblance to a certain coiffed, slick-haired cartoon is purely an affectionate tribute.