REST API reference

Complete reference for all mog.md REST API endpoints.

The mog REST API is available at https://api.mog.md. All endpoints are under /v1/. Requests and responses are JSON unless otherwise noted.

Base URL

https://api.mog.md

Authentication

Protected endpoints require a Bearer token in the Authorization header:

Authorization: Bearer <token>

Tokens are issued via the device code flow or retrieved from your dashboard. Different endpoints require different scopes:

ScopeRequired for
readEntitlements, reviews
purchasePurchases
downloadDownload URLs
sellVendor operations, uploads

Errors

Errors return a JSON body with a message field:

{ "message": "Listing not found" }
StatusMeaning
400Bad request / validation error
401Missing or invalid token
402Payment / approval required
403Insufficient token scope
404Resource not found
422Unprocessable entity
429Rate limit exceeded
500Internal server error

Health

GET /health

Returns API health status including database and Redis connectivity. No authentication required.

// All services healthy — HTTP 200
{
  "ok": true,
  "version": "0.1.0",
  "status": "healthy",
  "db": "ok",
  "redis": "ok"
}
 
// Degraded — HTTP 503
{
  "ok": false,
  "version": "0.1.0",
  "status": "degraded",
  "db": "error",
  "redis": "ok"
}

When status is "degraded", the HTTP status code is 503. Use this endpoint for liveness/readiness probes in your deployment platform.


Authentication endpoints

Start device flow

POST /v1/auth/device/start — Rate limit: 10/min per IP

Initiates a device code flow.

Response:

{
  "deviceCode": "internal-device-code",
  "userCode": "XKCD-7Z4B",
  "verificationUri": "https://mog.md/device",
  "expiresIn": 900,
  "interval": 5
}

Poll device flow

POST /v1/auth/device/poll — Rate limit: 10/min per IP

Poll for approval. Call every interval seconds.

Request body:

{ "deviceCode": "internal-device-code" }

Response:

// Still waiting
{ "status": "authorization_pending" }
 
// Code expired
{ "status": "expired" }
 
// Approved — store this token
{
  "status": "approved",
  "token": "mog_...",
  "tokenType": "Bearer",
  "expiresIn": 31536000
}

Approve device code

POST /v1/auth/device/approve — Auth: Bearer token — Rate limit: 10/min per IP

Request body:

{ "userCode": "XKCD-7Z4B" }

Response:

{ "ok": true }

Revoke token

POST /v1/auth/token/revoke — Auth: Bearer token

{ "ok": true }

List API tokens

GET /v1/auth/tokens — Auth: Bearer token

Returns all active tokens for the current user.

{
  "tokens": [
    {
      "id": "uuid",
      "name": "CLI device token",
      "scopes": ["read", "purchase", "download"],
      "policyId": null,
      "policyName": null,
      "lastUsedAt": "2026-01-15T10:00:00.000Z",
      "expiresAt": null,
      "revokedAt": null,
      "createdAt": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Revoke token by ID

DELETE /v1/auth/tokens/:id — Auth: Bearer token

{ "ok": true }

GET /v1/search — Rate limit: 60/min per IP

Search packages. No authentication required.

Query parameters:

ParameterTypeDescription
qstringFull-text search query
typeskill | rule | bundle | templateFilter by package type
targetcursor | claude-code | codex | genericFilter by agent target
sortpopular | recent | rated | price_asc | price_descSort order (default: popular)
pagenumberPage number (default: 1)
per_pagenumberResults per page (1–50, default: 20)
freebooleanOnly free packages

Response:

{
  "results": [
    {
      "id": "uuid",
      "vendorSlug": "acme",
      "slug": "router-eval",
      "title": "Router Eval Skill",
      "description": "...",
      "type": "skill",
      "targets": ["cursor", "claude-code"],
      "priceCents": 0,
      "currency": "usd",
      "latestVersion": "1.0.0",
      "installCount": 42,
      "mogRating": "4.8",
      "vendorName": "Acme Corp",
      "vendorVerified": false
    }
  ],
  "total": 1,
  "page": 1,
  "perPage": 20
}

Listings

Get listing

GET /v1/listings/:vendor/:slug

Get a single listing by vendor slug and package slug. No authentication required.

Get releases

GET /v1/listings/:vendor/:slug/releases

{
  "releases": [
    {
      "id": "uuid",
      "version": "1.1.0",
      "archiveSha256": "a3f8...",
      "publishedAt": "2026-01-15T10:00:00.000Z"
    }
  ]
}

Reviews

Get reviews

GET /v1/listings/:vendor/:slug/reviews

{
  "reviews": [
    {
      "id": "uuid",
      "userId": "uuid",
      "rating": 5,
      "body": "Excellent skill, saved me hours.",
      "createdAt": "2026-01-10T08:00:00.000Z"
    }
  ],
  "rating": 4.8,
  "ratingCount": 12
}

Create or update review

POST /v1/listings/:vendor/:slug/reviews — Auth: read scope

Request body:

{
  "rating": 5,           // required: 1–5
  "body": "Great skill!" // optional: max 2000 chars
}

Purchases & entitlements

Purchase a listing

POST /v1/purchases — Auth: purchase scope — Rate limit: 20/min per user

Request body:

{
  "listingId": "uuid",          // required
  "releaseId": "uuid",          // optional: defaults to latest published release
  "maxPriceCents": 1000         // optional: spend ceiling
}

Response (one of three shapes):

// Purchased
{
  "status": "purchased",
  "entitlementId": "uuid",
  "orderId": "uuid",
  "amountCents": 0
}
 
// Already owned
{ "status": "already_owned", "entitlementId": "uuid" }
 
// Approval required — HTTP 402
{
  "status": "approval_required",
  "approvalUrl": "https://mog.md/purchases/approve?listing=...",
  "reason": "Price (1500¢) exceeds your policy limit (1000¢)"
}

List entitlements

GET /v1/entitlements — Auth: read scope

{
  "entitlements": [
    {
      "id": "uuid",
      "listingId": "uuid",
      "releaseId": "uuid",
      "vendorSlug": "acme",
      "listingSlug": "router-eval",
      "listingTitle": "Router Eval Skill",
      "version": "1.0.0",
      "grantedAt": "2026-01-15T10:00:00.000Z"
    }
  ]
}

Check entitlement

GET /v1/entitlements/:listingId — Auth: read scope

// Owned — HTTP 200
{ "owned": true, "entitlement": { "...": "..." } }
 
// Not owned — HTTP 404
{ "owned": false }

List orders

GET /v1/orders — Auth: read scope

Returns the current user's order history.

{
  "orders": [
    {
      "id": "uuid",
      "listingId": "uuid",
      "releaseId": "uuid",
      "amountCents": 500,
      "status": "paid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "vendorSlug": "acme",
      "vendorName": "Acme Corp",
      "listingSlug": "router-eval",
      "listingTitle": "Router Eval Skill",
      "version": "1.0.0"
    }
  ]
}

Downloads

Get download URL

POST /v1/downloads — Auth: download scope

Request body:

{ "releaseId": "uuid" }

Response:

{
  "url": "https://packages.mog.md/releases/uuid.zip?X-Amz-Expires=300&...",
  "sha256": "a3f8c2d1...",
  "expiresAt": "2026-01-15T10:05:00.000Z"
}

User

Get current user

GET /v1/users/me — Auth: Bearer token

{
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "name": "Ada Lovelace",
    "avatarUrl": "https://...",
    "emailVerified": true,
    "createdAt": "2026-01-01T00:00:00.000Z"
  }
}

Vendor

Create or update vendor profile

POST /v1/vendor/profile — Auth: sell scope

Request body:

{
  "slug": "acme",              // 2–32 chars, lowercase letters/numbers/hyphens
  "displayName": "Acme Corp",  // 1–64 chars
  "bio": "We make skills.",    // optional, max 500 chars
  "website": "https://acme.io" // optional, must be valid URL
}

Start Stripe Connect onboarding

POST /v1/vendor/onboard — Auth: sell scope

{ "url": "https://connect.stripe.com/setup/..." }

Upload release

POST /v1/vendor/releases — Auth: sell scope — Rate limit: 5/min per user

Request: multipart/form-data with:

FieldTypeDescription
archivefile (.zip)Package archive
priceCentsnumberPrice in cents (0 for free)

Response:

{
  "release": {
    "id": "uuid",
    "version": "1.0.0",
    "scanStatus": "pending",
    "createdAt": "2026-01-15T10:00:00.000Z"
  },
  "listing": { "id": "uuid", "slug": "my-skill" }
}

Publish release

PATCH /v1/vendor/releases/:id/publish — Auth: sell scope

{ "ok": true, "version": "1.0.0" }

Update listing

PATCH /v1/vendor/listings/:id — Auth: sell scope

Request body (all fields optional):

{
  "title": "My Awesome Skill",  // 1–120 chars
  "description": "...",          // 1–1024 chars
  "tags": ["react", "testing"],  // max 10 tags
  "priceCents": 500,             // 0 = free
  "readmeMd": "# My Skill\n...", // raw Markdown for listing page
  "status": "published"         // optional: "published" | "unlisted" | "draft"
}

Get vendor analytics

GET /v1/vendor/analytics — Auth: sell scope

{
  "vendor": {
    "slug": "acme",
    "displayName": "Acme Corp",
    "stripeOnboardingComplete": true
  },
  "revenue": {
    "totalCents": 15000,
    "totalOrders": 42
  },
  "listings": [
    {
      "id": "uuid",
      "slug": "router-eval",
      "title": "Router Eval Skill",
      "priceCents": 500,
      "installCount": 42,
      "orderCount": 38,
      "revenueCents": 14250
    }
  ]
}

Spend policies

Spend policies control what agents can purchase autonomously. See Spend policies for the full reference.

List policies

GET /v1/policies — Auth: Bearer token

{
  "policies": [
    {
      "id": "uuid",
      "name": "CI agent policy",
      "maxPerPurchaseCents": 1000,
      "dailyLimitCents": 5000,
      "monthlyLimitCents": 20000,
      "requireApprovalAboveCents": 500,
      "vendorAllowlist": [],
      "blockedTypes": [],
      "active": true,
      "createdAt": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Create policy

POST /v1/policies — Auth: Bearer token

Request body:

{
  "name": "CI agent policy",          // required, 1–128 chars
  "maxPerPurchaseCents": 1000,         // optional, cents
  "dailyLimitCents": 5000,            // optional
  "monthlyLimitCents": 20000,         // optional
  "requireApprovalAboveCents": 500,   // optional — require user approval above this price
  "vendorAllowlist": [],              // optional — empty = all vendors allowed
  "blockedTypes": ["bundle"],         // optional — block specific package types
  "active": true                      // optional, default: true
}

Update policy

PATCH /v1/policies/:id — Auth: Bearer token

Same fields as create, all optional. Returns updated policy.

Delete policy

DELETE /v1/policies/:id — Auth: Bearer token

{ "ok": true }

Leaderboard

GET /v1/leaderboard

Get the package leaderboard. No authentication required.

Query parameters:

ParameterTypeDescription
windowall | trending | hotRanking window (default: all)
typestringFilter by package type
limitnumberMax results (default: 20, max: 100)
offsetnumberPagination offset
{
  "entries": [
    {
      "rank": 1,
      "vendor": "acme",
      "slug": "router-eval",
      "title": "Router Eval Skill",
      "type": "skill",
      "vendorVerified": true,
      "mogRating": 4.8,
      "installCount": 1240
    }
  ],
  "total": 42
}

Audits

GET /v1/audits

Get scan/audit results for published packages. No authentication required. Useful for transparency tooling and dashboards.

Query parameters:

ParameterTypeDescription
typestringFilter by package type
statuspassed | flagged | failed | pendingFilter by scan status
limitnumberMax results (default: 20, max: 100)
offsetnumberPagination offset
{
  "audits": [
    {
      "rank": 1,
      "vendor": "acme",
      "slug": "router-eval",
      "title": "Router Eval Skill",
      "type": "skill",
      "vendorVerified": true,
      "mogRating": 4.8,
      "installCount": 124,
      "scan": {
        "status": "passed",
        "label": "safe",
        "qualityScore": 87,
        "version": "1.0.0",
        "scannedAt": "2026-01-15T10:00:00.000Z"
      },
      "promptGuard": {
        "ran": true,
        "verified": true,
        "perFile": [{ "file": "SKILL.md", "label": "BENIGN", "score": 0.99 }]
      }
    }
  ],
  "total": 42,
  "generatedAt": "2026-02-23T18:00:00.000Z"
}

Webhooks

Stripe webhook

POST /v1/webhooks/stripe

Stripe webhook handler. Verify with the stripe-signature header.

EventEffect
payment_intent.succeededGrants entitlement, marks order as paid
payment_intent.payment_failedMarks order as failed
account.updatedUpdates vendor Stripe onboarding status
{ "received": true }