Skip to content
Mogger

API reference

One endpoint scores one image or one text against a formula and returns a calibrated score and a boolean. This page covers every endpoint, limit, and error, plus setup for agent harnesses.

Base URLhttps://mogger.dev/api/v1

Introduction

Mogger is System 1 preferences for AI harnesses. A human tunes a preference once: a formula, which is one yes or no question about one input, and a pass/fail boundary kept on the server. An agent harness (Claude Code, a Grok bot, any workflow) then calls it at high volume, low cost, and low latency, and gets back a calibrated score and a boolean, with no pushback from a sassy System 2 model.

System 1 is the fast, intuitive mode of thought and System 2 the slow, deliberate one. Mogger hands an agent the first kind, tuned by a person.

Each call is a single forward pass of a decision model. No tokens are generated, so the answer is a probability, not prose. A response carries noul, the probability from 0 to 1 that the answer is yes, and mogs, the verdict. The boundary between the two verdicts is set on the server for each formula and may change. It is never returned, so an agent has nothing to argue with.

Every formula judges one input on its own. None compares two inputs: to choose between candidates, score each one and sort by noul.

The API speaks JSON over HTTPS. CORS is open on /api/v1, every response carries a request id, and every authenticated response reports what is left of the key's quota.

A shaggy black dog in a cobalt blue cardigan typing on a small beige retro computer.
Quick start
curl https://mogger.dev/api/v1/mog \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "formula": "bio-mog",
    "text": "I restore 1970s synthesizers for a living. 312 repaired, 4 ruined."
  }'

Authentication

Scoring calls and custom formula calls need an API key, sent as a bearer token in the Authorization header. Listing and reading built-in formulas needs none.

Keys are invite only. Email [email protected] to ask for one. Check a key and its quota at any time with GET /api/v1/me, which is free.

A key is mog_ followed by 32 letters and digits. Mogger stores only its SHA-256 hash, so the key is shown once, when it is created, and a lost key cannot be recovered. Ask for a rotation instead: it revokes the old key and issues a new one with the same label.

Keep keys off the client

CORS is open, so a key placed in a public web page is readable by anyone who loads it. Keep keys on a server or in your harness's environment as MOGGER_API_KEY.
Request header
Authorization: Bearer mog_...
401 Unauthorized
{
  "error": {
    "code": "unauthorized",
    "message": "Missing API key. Send Authorization: Bearer mog_..."
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Quotas and cost

Each key has a lifetime budget of units, 1,000 unless it was issued with another. The budget never resets.

RequestUnits
Score text1
Score an image (with or without text)10
Create, edit, list, or delete a custom formula0
Read formulas, check a key0
  • A request is charged once, however many formulas it names. Batch up to 8 formulas for one input into one call.
  • Only success is charged. A request that fails validation costs nothing, and if the model call fails the units are refunded (502 upstream_error).
  • When the remaining budget cannot cover a request, the API answers 402 quota_exhausted and charges nothing. The body carries the quota, so an image request can fail while a text request still fits.
  • A spent key stays spent until it is rotated. Rotation issues a new key with a fresh budget and the same label, and your custom formulas move with it.
  • There is no rate limit. The quota is the only brake, so plan batches and cache results by formula, version, and a hash of the input.
Response headers
HTTP/1.1 402 Payment Required
X-Mogger-Request-Id: req_...
X-Mogger-Quota-Remaining: 4
X-Mogger-Quota-Budget: 1000
402 quota_exhausted
{
  "quota": {
    "remaining": 4,
    "budget": 1000
  },
  "error": {
    "code": "quota_exhausted",
    "message": "This key has 4 of 1000 units left and this request costs 10. Ask for a rotated key"
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Score an input

POST/api/v1/mogKey required1 or 10 units

Scores one input against 1 to 8 formulas in a single forward pass and returns one result per formula. The request is charged once.

Body parameters

formulastringOne of formula or formulas
One formula: a built-in slug such as bio-mog, or a custom formula id such as f_7Kq2xW9pLm.
formulasstring[]One of formula or formulas
1 to 8 unique slugs or ids that share one input type. Charged once, like a single formula.
textstringRequired by text formulas
Up to 10,000 characters, counted as Unicode code points after leading and trailing whitespace is trimmed. With image formulas it is optional context sent along with the image.
imagestringRequired by image formulas
A base64 data URL such as data:image/png;base64,.... PNG, JPEG, or WebP, at most 3 MiB (3,145,728 bytes) decoded, and the bytes must match the declared type. URLs are not fetched. Text formulas reject it.

Input rules

  • Every formula in a request shares one input type. Mixing image and text formulas returns 400 invalid_request.
  • An image formula without image, or a text formula with image or without text, returns 400 input_mismatch. Text formulas refuse images so a text judgment is never billed at the image rate.
  • Unknown fields are rejected, and a field set to null counts as absent.
  • A body over 4,400,000 bytes is rejected with 413 payload_too_large before it is read.
  • A request that fails any of these checks is never charged.
POST /api/v1/mog
curl https://mogger.dev/api/v1/mog \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "formula": "bio-mog",
    "text": "I restore 1970s synthesizers for a living. 312 repaired, 4 ruined."
  }'
Response
{
  "results": {
    "bio-mog": {
      "mogs": true,
      "noul": 0.94,
      "version": 1
    }
  },
  "cost": 1,
  "quota": {
    "remaining": 999,
    "budget": 1000
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Score an image with several formulas

Name every formula you want for one image in formulas. The image is sent to the model once, and the request costs 10 units whether it names one formula or 8. Results come back keyed by slug.

Send the image inline as a base64 data URL. Downscale large photos to about 1024 px on the long side before encoding: it keeps the body small and well under the 3 MiB (3,145,728 bytes) cap. In a shell, build the body in a file, since a base64 image is too long for a command line argument.

To score many images, send one request per image. To score many strings, send one request per string at 1 unit each.

POST /api/v1/mog
# Build the body in a file: a base64 image is too long for a shell argument.
# For a PNG or WebP file, change image/jpeg to image/png or image/webp.
{
  printf '{"formulas":["jawline","canthal-tilt"],"image":"data:image/jpeg;base64,'
  base64 < photo.jpg | tr -d '\n'
  printf '"}'
} > body.json

curl https://mogger.dev/api/v1/mog \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @body.json
Response
{
  "results": {
    "jawline": {
      "mogs": true,
      "noul": 0.91,
      "version": 1
    },
    "canthal-tilt": {
      "mogs": false,
      "noul": 0.08,
      "version": 1
    }
  },
  "cost": 10,
  "quota": {
    "remaining": 989,
    "budget": 1000
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

The response

resultsobject
One entry per formula, keyed by the slug or id you sent.
mogsboolean
The verdict. true means the input mogs.
noulnumber
Calibrated probability, from 0 to 1, that the answer to the formula's question is yes. Use it to rank candidates or to flag close calls for a human.
versioninteger
The formula revision that produced the score. It changes when the formula's question changes, and scores from different versions are not comparable.
costinteger
Units charged: 1 without an image, 10 with one.
quotaobject
remaining and budget for the key, after this request.
request_idstring
req_ plus 24 characters. Quote it when reporting a problem.

The boundary stays on the server

mogs compares noul with a pass/fail boundary set on the server for each formula. The boundary is never returned and may change, so the same score can carry a different verdict later. Treat mogs as the decision.

Response headers

  • X-Mogger-Request-Id: The request id, also in the body as request_id. Quote it when reporting a problem.
  • X-Mogger-Quota-Remaining: Units left on the key after this request.
  • X-Mogger-Quota-Budget: The key's lifetime budget in units.
  • Cache-Control: no-store. Scoring responses are never cached.
TypeScript
type MogRequest = {
  /** One slug or custom id. Send this or formulas. */
  formula?: string;
  /** 1 to 8 unique slugs or ids, all with the same input type. */
  formulas?: string[];
  /** Up to 10,000 characters. */
  text?: string;
  /** "data:image/png;base64,...", PNG, JPEG, or WebP, 3 MiB decoded at most. */
  image?: string;
};

type MogResponse = {
  /** One entry per formula, keyed by the slug or id you sent. */
  results: Record<string, MogResult>;
  /** Units charged: 1 for text, 10 with an image. */
  cost: number;
  quota: { remaining: number; budget: number };
  request_id: string;
};

type MogResult = {
  /** The verdict. */
  mogs: boolean;
  /** Calibrated probability that the answer is yes, 0 to 1. */
  noul: number;
  /** Formula revision. Scores from different versions are not comparable. */
  version: number;
};
400 input_mismatch
{
  "error": {
    "code": "input_mismatch",
    "message": "bio-mog is a text formula and cannot take an image. Remove image"
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Score one formula by path

POST/api/v1/mog/{idOrSlug}Key required1 or 10 units

Scores one formula named in the path: a custom formula id from POST /api/v1/formulas, or a built-in slug such as bio-mog. The url field of a custom formula points here.

textstringRequired by text formulas
Same rules as POST /api/v1/mog.
imagestringRequired by image formulas
Same rules as POST /api/v1/mog.

A formula or formulas field in this body is 400 invalid_request: the path already names the formula. The response has the same shape and headers as POST /api/v1/mog, with results keyed by the id or slug. To batch a custom formula with others, put its id in formulas instead. See the formula gallery for built-in slugs.

POST /api/v1/mog/{idOrSlug}
curl https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "I restore 1970s synthesizers for a living. 312 repaired, 4 ruined."
  }'

List formulas

GET/api/v1/formulasNo keyFree

Returns every built-in formula in display order as { "formulas": [...] }. Custom formulas are never listed here. Responses may be cached for a few minutes.

The formula object

idstring
Same as slug for built-in formulas. Custom formulas use an f_ id.
kindstring
builtin here. Custom formulas are custom.
slugstring
What you send as formula. Stable.
name, tagline, descriptionstring
What the formula judges, in plain words.
categorystring
Face, Physique, Style, or Text.
inputstring
image or text: which input the formula requires.
lookForstring[]
The features the formula weighs, 3 to 5 short lines.
versioninteger
Current revision. Matches version in scoring results.
examplesobject[]
Demo inputs. Image examples carry src (a path on this site), alt, credit, license, and sourceUrl. Text examples carry text. baked is a score computed ahead of time, { noul, mogs }, or null when the example is not scored yet.

Built-in formulas

SlugNameInput
jawlineJawlineImage input
canthal-tiltCanthal TiltImage input
hunter-eyesHunter EyesImage input
hairlineHairlineImage input
skinSkinImage input
overall-faceOverall FaceImage input
fit-checkFit CheckImage input
physiquePhysiqueImage input
bio-mogBio MogText input
tweet-mogTweet MogText input
resume-mogResume MogText input
codebase-mogCodebase MogText input

Each formula has a page with its examples, for instance Jawline.

GET /api/v1/formulas
curl https://mogger.dev/api/v1/formulas \
  | jq -r '.formulas[] | [.slug, .input, .name] | @tsv'
Response, trimmed to one formula and one example
{
  "formulas": [
    {
      "id": "jawline",
      "kind": "builtin",
      "slug": "jawline",
      "name": "Jawline",
      "tagline": "Does the jaw draw one clean, defined line from ear to chin?",
      "description": "Scores the lower third of a face: how sharply the jaw is defined, how cleanly it separates from the neck, and how well the chin projects. A beard or camera angle counts only for what it lets the model see.",
      "category": "Face",
      "input": "image",
      "lookFor": [
        "A visible jaw edge running from ear to chin",
        "A defined angle at the back of the jaw",
        "Forward chin projection that balances the face",
        "A clean break between the jaw and the neck"
      ],
      "version": 1,
      "examples": [
        {
          "kind": "image",
          "id": "david",
          "label": "Michelangelo's David, plaster cast",
          "src": "/examples/david-head-cast.jpg",
          "alt": "Plaster cast of the head of Michelangelo's David, three-quarter view facing right, on a black background",
          "credit": "Michelangelo (sculptor). Plaster cast, Statens Museum for Kunst",
          "license": "Public domain",
          "sourceUrl": "https://commons.wikimedia.org/wiki/File:Michelangelo_Buonarroti,_Hoved_fra_statuen_af_David,_,_KAS2232,_Statens_Museum_for_Kunst.jpg",
          "baked": {
            "noul": 0.8927,
            "mogs": true
          }
        }
      ]
    }
  ]
}

Retrieve a formula

GET/api/v1/formulas/{idOrSlug}No key for built-insFree

Returns one built-in formula as { "formula": {...} }, the same formula object that List formulas returns. An unknown slug returns 404 not_found.

A custom formula id needs a key, and comes back in the same { "formula": {...} } wrapper. The creator's key gets the full creator view, with prompt and criteria. Any other key gets the public view without them. Without a key, a custom id returns 404 not_found, as if it did not exist. See Custom formulas.

GET /api/v1/formulas/jawline
curl https://mogger.dev/api/v1/formulas/jawline
404 not_found
{
  "error": {
    "code": "not_found",
    "message": "No formula with slug \"jawlin\". GET /api/v1/formulas lists valid slugs"
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Custom formulas

Any valid key can capture a preference as its own formula and call it by id. Write the question once, then let an agent apply it at volume. The create page does the same in the browser: write, test, edit, and delete your formulas with your key.

  • The id is f_ plus 10 random letters and digits, such as f_7Kq2xW9pLm. It works anywhere a slug does: in formula, in formulas next to built-in formulas of the same input type (8 at most, charged once), and in POST /api/v1/mog/{id}.
  • Custom formulas are unlisted: never in the gallery, llms.txt, the sitemap, or GET /api/v1/formulas. The id acts as a capability. Anyone who holds it can score with it, billed to their own key, so share it on purpose.
  • The prompt and criteria are returned only to the key that created the formula. Everyone else sees the title, input, and version. Once the creator locks the formula, no key sees them again, the creator included.
  • The global pass/fail boundary applies. Creators never see or set it.
  • Creating, editing, listing, and deleting cost 0 units. Scoring costs the same as a built-in formula.
  • A key can own 25. Rotating the key keeps ownership, and a formula keeps working for other keys if its creator's key is revoked.

Writing a prompt

Ask one absolute yes or no question about one input, where yes means it passes. Name the observable features that decide it and say how to treat edge cases. Never compare: the model sees one input at a time. The title is for people only and is never sent to the model.
Custom formula object
{
  "id": "f_7Kq2xW9pLm",
  "object": "formula",
  "kind": "custom",
  "title": "Clean product photo",
  "input": "image",
  "prompt": "Is this a clean product photo: one product, centered, evenly lit, on a plain background, with nothing distracting in frame?",
  "criteria": {
    "true": "One product on a plain background, sharp and evenly lit.",
    "false": "Cluttered, dim, off-center, or more than one subject."
  },
  "locked": false,
  "locked_at": null,
  "version": 1,
  "url": "https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm",
  "created_at": "2026-11-07T18:20:00.000Z",
  "updated_at": "2026-11-07T18:20:00.000Z"
}
Seen by any other key
{
  "id": "f_7Kq2xW9pLm",
  "object": "formula",
  "kind": "custom",
  "title": "Clean product photo",
  "input": "image",
  "locked": false,
  "locked_at": null,
  "version": 1,
  "url": "https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm",
  "created_at": "2026-11-07T18:20:00.000Z",
  "updated_at": "2026-11-07T18:20:00.000Z"
}

Create a custom formula

POST/api/v1/formulasKey requiredFree
titlestringRequired
1 to 80 characters. Display only: never sent to the model.
promptstringRequired
10 to 2,000 characters. The yes or no question the model answers about each input.
inputstringRequired
text or image.
criteriaobject
Optional. One or two sentences for each pole sharpen the judgment.
truestringRequired
What a passing input looks like, up to 500 characters.
falsestringRequired
What a failing input looks like, up to 500 characters.

Returns 201 with the creator view, including id and url. A key that already owns 25 custom formulas gets 409 formula_limit_reached. Test the new formula on a few examples you already have a verdict for before using it at volume.

POST /api/v1/formulas
curl https://mogger.dev/api/v1/formulas \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Clean product photo",
    "prompt": "Is this a clean product photo: one product, centered, evenly lit, on a plain background, with nothing distracting in frame?",
    "input": "image",
    "criteria": {
      "true": "One product on a plain background, sharp and evenly lit.",
      "false": "Cluttered, dim, off-center, or more than one subject."
    }
  }'
201 Created
{
  "id": "f_7Kq2xW9pLm",
  "object": "formula",
  "kind": "custom",
  "title": "Clean product photo",
  "input": "image",
  "prompt": "Is this a clean product photo: one product, centered, evenly lit, on a plain background, with nothing distracting in frame?",
  "criteria": {
    "true": "One product on a plain background, sharp and evenly lit.",
    "false": "Cluttered, dim, off-center, or more than one subject."
  },
  "locked": false,
  "locked_at": null,
  "version": 1,
  "url": "https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm",
  "created_at": "2026-11-07T18:20:00.000Z",
  "updated_at": "2026-11-07T18:20:00.000Z"
}

List your custom formulas

GET/api/v1/formulas/mineKey requiredFree

Returns the custom formulas your key created, newest first, as { "formulas": [...] }. Each entry is the full creator view shown in Custom formulas, except that a locked entry has no prompt and no criteria. Formulas created before a key rotation are included.

GET /api/v1/formulas/mine
curl https://mogger.dev/api/v1/formulas/mine \
  -H "Authorization: Bearer $MOGGER_API_KEY"

Update a custom formula

PATCH/api/v1/formulas/{id}Creator keyFree

Send only the fields to change: title, prompt, input, or criteria, with the same limits as on create. Set criteria to null to remove it. Returns the updated creator view.

Changing prompt, input, or criteria bumps version, because scores from the old question are no longer comparable. A title edit does not. Another key gets 403 forbidden, and an unknown id 404 not_found.

On a locked formula only title can change. A body with prompt, input, or criteria gets 403 forbidden.

PATCH /api/v1/formulas/f_7Kq2xW9pLm
curl -X PATCH https://mogger.dev/api/v1/formulas/f_7Kq2xW9pLm \
  -H "Authorization: Bearer $MOGGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Is this a clean product photo with exactly one product, centered, on a plain background?"
  }'
Response
{
  "id": "f_7Kq2xW9pLm",
  "object": "formula",
  "kind": "custom",
  "title": "Clean product photo",
  "input": "image",
  "prompt": "Is this a clean product photo with exactly one product, centered, on a plain background?",
  "criteria": {
    "true": "One product on a plain background, sharp and evenly lit.",
    "false": "Cluttered, dim, off-center, or more than one subject."
  },
  "locked": false,
  "locked_at": null,
  "version": 2,
  "url": "https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm",
  "created_at": "2026-11-07T18:20:00.000Z",
  "updated_at": "2026-11-07T18:41:00.000Z"
}

Lock a custom formula

POST/api/v1/formulas/{id}/lockCreator keyFreeOne way

Locks the formula for good. No body. Returns 200 with the creator view, now with locked set to true, locked_at set to the time of the lock, and no prompt or criteria keys. Locking a formula that is already locked returns the same view.

  • From then on no response carries the prompt or criteria, to any key, the creator included. Use it before you share an id, even inside a team that shares one key.
  • Scoring by id keeps working for every key, with the same version, so scores stay comparable.
  • The title can still change and the formula can still be deleted. The prompt, input, and criteria can never change again: create a new formula to change the question.
  • There is no unlock. Keep your own copy of the prompt first, because it cannot be read back.

Only the creator's key can lock: another key or a built-in slug gets 403 forbidden, a missing key 401 unauthorized, and an unknown id 404 not_found.

POST /api/v1/formulas/f_7Kq2xW9pLm/lock
curl -X POST https://mogger.dev/api/v1/formulas/f_7Kq2xW9pLm/lock \
  -H "Authorization: Bearer $MOGGER_API_KEY"
Response
{
  "id": "f_7Kq2xW9pLm",
  "object": "formula",
  "kind": "custom",
  "title": "Clean product photo",
  "input": "image",
  "locked": true,
  "locked_at": "2026-11-07T19:05:00.000Z",
  "version": 1,
  "url": "https://mogger.dev/api/v1/mog/f_7Kq2xW9pLm",
  "created_at": "2026-11-07T18:20:00.000Z",
  "updated_at": "2026-11-07T19:05:00.000Z"
}

Delete a custom formula

DELETE/api/v1/formulas/{id}Creator keyFree

Deletes the formula for good and returns { "id": "...", "deleted": true }. Every key that used the id loses it: later calls return 400 unknown_formula. Only the creator's key can delete (403 forbidden otherwise).

DELETE /api/v1/formulas/f_7Kq2xW9pLm
curl -X DELETE https://mogger.dev/api/v1/formulas/f_7Kq2xW9pLm \
  -H "Authorization: Bearer $MOGGER_API_KEY"
Response
{
  "id": "f_7Kq2xW9pLm",
  "deleted": true
}

Check a key

GET/api/v1/meKey requiredFree

Returns the key's label and quota. It is never charged, so check it before a large batch. A revoked key still gets an answer, with revoked: true, so its holder can see why calls fail.

labelstring
The name the key was issued under.
prefixstring
The first 8 characters of the key, safe to log.
budget, used, remaininginteger
Lifetime units: the total, what has been spent, and what is left.
revokedboolean
true once the key is revoked or replaced by a rotation.
created_at, last_used_atstring
ISO 8601 timestamps. last_used_at is null until the first successful call.
GET /api/v1/me
curl https://mogger.dev/api/v1/me \
  -H "Authorization: Bearer $MOGGER_API_KEY"
Response
{
  "label": "build-bot",
  "prefix": "mog_a1B2",
  "budget": 1000,
  "used": 37,
  "remaining": 963,
  "revoked": false,
  "created_at": "2026-11-07T18:20:00.000Z",
  "last_used_at": "2026-11-07T19:02:11.000Z"
}

Errors

Errors use HTTP status codes and a JSON body with a stable code and a readable message. Branch on code: messages may change. Every error body carries request_id, and a 402 also carries quota.

StatusCodeWhenWhat to do
400invalid_requestThe body is not a JSON object, has an unknown field, names no formula or more than 8, repeats a formula, mixes image and text formulas, or breaks a limit. Also sent with status 405 when the method is wrong for the path.Fix the request. Sending it again unchanged returns the same error.
400input_mismatchAn image formula got no image, or a text formula got an image or no text.Match the input to each formula's input field.
400unknown_formulaA slug or custom id in formula or formulas does not exist.List valid slugs with GET /api/v1/formulas.
401unauthorizedThe Authorization header is missing, malformed, or names an unknown key.Send Authorization: Bearer mog_...
401key_revokedThe key was revoked, or replaced by a rotation.Ask for a new key.
402quota_exhaustedThe key's lifetime budget cannot cover this request. The body also carries quota.Stop. Ask for a rotated key. A cheaper text request may still fit.
403forbiddenA key other than the creator's tried to edit or delete a custom formula.Use the key that created the formula.
404not_foundNo endpoint at that path, no formula with that slug, or a custom id requested without a key.Check the path. Send a key to read a custom formula.
409formula_limit_reachedThe key already owns 25 custom formulas.Delete one before creating another.
413payload_too_largeThe body is over 4,400,000 bytes, or the decoded image is over 3 MiB (3,145,728 bytes).Downscale the image. 1024 px on the long side is plenty.
500internal_errorAn unexpected fault on the server.Retry once after a short wait, then report the request_id.
502upstream_errorThe scoring model call failed. Nothing is charged: the units are refunded.Retry with backoff.

Retrying

  • Never retry a 4xx response unchanged. It will fail the same way. A 402 means stop and ask for a rotated key.
  • Retry 502 upstream_error with backoff. It was not charged.
  • Retry 500 internal_error once, then report the request_id.
  • A method the path does not support returns 405 with code invalid_request and an Allow header.
Error body
{
  "quota": {
    "remaining": 4,
    "budget": 1000
  },
  "error": {
    "code": "quota_exhausted",
    "message": "This key has 4 of 1000 units left and this request costs 10. Ask for a rotated key"
  },
  "request_id": "req_0MHmB7xKQ2c9tVwLp4ZsYd3R"
}

Install the skill

One file, SKILL.md, teaches Claude Code and Codex to call Mogger. It drives every endpoint with curl and jq, and encodes images in the shell, so image bytes never enter the model's context. There is no MCP server and no npm package to install.

The installer is one command. It does three things:

  • It installs the skill to ~/.claude/skills/mogger/SKILL.md and ~/.codex/skills/mogger/SKILL.md. When a different file is already there, it is kept as SKILL.md.bak and the installer says so.
  • It reads the key from MOGGER_API_KEY, or asks for it without echoing, and saves it with owner only permissions to ${XDG_CONFIG_HOME:-$HOME/.config}/mogger/key.
  • It checks the key with GET /api/v1/me and prints the label and units left. It warns when jq is missing and never edits your shell startup files.

To install by hand, download the one file into either skills folder and set MOGGER_API_KEY. The skill looks for the key in that variable first, then in the key file.

Read before you pipe

Piping a script into sh runs it as you. The installer is plain POSIX sh: open mogger.dev/install.sh to read it first.
Install
curl -fsSL https://mogger.dev/install.sh | sh
Uninstall
curl -fsSL https://mogger.dev/install.sh | sh -s -- --uninstall

Using Mogger from Claude Code

With the skill installed, ask in plain words: does this headshot mog on jawline, which of these bios mog, sort this folder of photos by fit check. The skill picks formulas whose input matches, batches up to 8 per request, prints a summary table, and stops on a 402. It treats the boolean as final instead of reasoning its way around it.

To make Mogger a project habit, add the block on the right to the project's CLAUDE.md. Codex reads the same text from AGENTS.md.

When a person keeps making the same kind of call by hand (sorting photos, picking names, triaging candidates), the skill offers once to capture that preference as a custom formula, then tests it on examples the person already judged.

CLAUDE.md
## Mogger

For fast yes or no judgments of an image or a piece of text (does this photo, bio, or draft pass?), use the mogger skill instead of judging it yourself.

- Pick formulas whose input matches: image formulas take an image, text formulas take text. The list is at https://mogger.dev/llms.txt.
- Batch up to 8 formulas for one input into one request. Text costs 1 unit, an image 10.
- Use `mogs` as the decision and `noul` (0 to 1) to rank. Do not argue with a verdict or rerun a call to change it.
- Never print the key. Never retry a 4xx. On a 402, stop and tell me the key needs a rotation.

Other harnesses

Any harness with function calling can use Mogger as a tool: a Grok bot, an OpenAI or Anthropic agent loop, a workflow engine. Give the model a tool definition, and have your code make the HTTP call.

  • Let the model name a file path, not paste image data. Your handler reads and encodes the file, which keeps the model's context small and the image out of it.
  • Return only results to the model. The verdict is the answer; there is nothing to deliberate.
  • The definition uses JSON Schema. Anthropic's Messages API calls the schema field input_schema. OpenAI style APIs nest it as function.parameters.
  • To offer a custom formula, add its id to the enum.
Tool definition
{
  "name": "mogger_score",
  "description": "Fast, calibrated yes or no judgment of one image or one text against Mogger formulas. Returns mogs (the verdict) and noul (0 to 1) for each formula. Use mogs as the decision.",
  "parameters": {
    "type": "object",
    "properties": {
      "formulas": {
        "type": "array",
        "description": "1 to 8 formulas that share one input type. Image formulas: jawline, canthal-tilt, hunter-eyes, hairline, skin, overall-face, fit-check, physique. Text formulas: bio-mog, tweet-mog, resume-mog, codebase-mog.",
        "items": {
          "type": "string",
          "enum": ["jawline", "canthal-tilt", "hunter-eyes", "hairline", "skin", "overall-face", "fit-check", "physique", "bio-mog", "tweet-mog", "resume-mog", "codebase-mog"]
        },
        "minItems": 1,
        "maxItems": 8,
        "uniqueItems": true
      },
      "text": {
        "type": "string",
        "maxLength": 10000,
        "description": "The text to judge. Required for text formulas."
      },
      "image_path": {
        "type": "string",
        "description": "Path to a PNG, JPEG, or WebP file to judge. Required for image formulas."
      }
    },
    "required": ["formulas"]
  }
}
Handler (JavaScript)
import { readFile } from "node:fs/promises";
import { extname } from "node:path";

const MIME = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };

// Runs when the model calls mogger_score. The model names a file; this code
// reads and encodes it, so image bytes never enter the model's context.
export async function moggerScore({ formulas, text, image_path }) {
  const body = { formulas };
  if (text) body.text = text;
  if (image_path) {
    const mime = MIME[extname(image_path).toLowerCase()];
    if (!mime) return { error: "unsupported_image_type" };
    body.image = `data:${mime};base64,${(await readFile(image_path)).toString("base64")}`;
  }
  const res = await fetch("https://mogger.dev/api/v1/mog", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MOGGER_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) return { error: data.error.code, message: data.error.message };
  return data.results;
}

Machine-readable docs

/llms.txt, /ai.txt, and /si.txt serve the same plain text, byte for byte: the contract, costs, limits, error codes, the custom formula endpoints, and every built-in formula with its slug and input type. Point an agent at any of them.

/api/v1/formulas returns the formula list as JSON, and /skills/mogger/SKILL.md is the agent skill.

Fetch
curl https://mogger.dev/llms.txt