Sign inGet started

Designs and reviews REST API endpoints with best practices for URLs, status codes, pagination, and more.

Lift-13ppscreen tier · directional
92
Trustsecurity · quality · license · upkeep
Scan Saferisk 0/100
LicenseMITserved via MCP
Skill-matched tasks (generated, public + held-out) · glm-5.3-flash@cf
-13pp95% CI [-38, 0]unverified · screen tier
baseline 88% → skill 75% pass
trigger rate 100% · 24 trials

A/B outputs — median trial per arm

Same task, same model: without the skill (left) vs with it (right).

api-design--m1
baseline · 1.00
# Task Management REST API

## Resource
`/tasks` — collection; `/tasks/{id}` — individual task.

## Endpoints

| Action | Method & Path | Success | Errors |
|---|---|---|---|
| Create a task | `POST /tasks` | `201 Created` (return the created task, with `Location: /tasks/{id}` header) | `400 Bad Request` (invalid body, e.g. missing title or malformed due date) |
| List all tasks | `GET /tasks` | `200 OK` (JSON array of tasks) | — |
| Get one task | `GET /tasks/{id}` | `200 OK` | `404 Not Found` (id doesn't exist) |
| Update a task | `PUT /tasks/{id}` (full replace) or `PATCH /tasks/{id}` (partial update) | `200 OK` (return updated task) | `400 Bad Request` (invalid fields), `404 Not Found` |
| Delete a task | `DELETE /tasks/{id}` | `204 No Content` (empty body) | `404 Not Found` |

## Task representation (JSON)

```json
{
  "id": "a1b2c3",
  "title": "Buy groceries",
  "completed": false,
  "dueDate": "2025-07-01"
}
```

## Notes
- Use `PATCH` if clients often toggle just `completed`; use `PUT` for full-object updates (or support both).
- Return `405 Method Not Allowed` for unsupported methods on a path, and `400` with a descriptive error body for validation failures.
- Optional extras: filtering/sorting via query params on `GET /tasks` (e.g. `?completed=false&dueBefore=2025-07-01`), and `400` for a non-UUID/invalid `{id}` format if you validate ids.
with skill · 1.00
# Task Management REST API

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/tasks` | Create a task |
| GET | `/api/v1/tasks` | List all tasks |
| GET | `/api/v1/tasks/:id` | Get one task |
| PATCH | `/api/v1/tasks/:id` | Partially update a task |
| PUT | `/api/v1/tasks/:id` | Fully replace a task (optional) |
| DELETE | `/api/v1/tasks/:id` | Delete a task |

## Status Codes

**POST /api/v1/tasks**
- `201 Created` — task created; return the task body and a `Location: /api/v1/tasks/:id` header
- `400 Bad Request` — malformed JSON
- `422 Unprocessable Entity` — valid JSON but invalid data (e.g., missing title, bad due date format)

**GET /api/v1/tasks**
- `200 OK` — array of tasks
- Optional filtering: `GET /api/v1/tasks?completed=false&sort=due_date`

**GET /api/v1/tasks/:id**
- `200 OK` — task found
- `404 Not Found` — no task with that id

**PATCH /api/v1/tasks/:id**
- `200 OK` — updated, return the updated task
- `400` / `422` — validation errors
- `404 Not Found` — task doesn't exist

**DELETE /api/v1/tasks/:id**
- `204 No Content` — deleted, empty body
- `404 Not Found` — task doesn't exist

## Example Payloads

Create request:
```json
POST /api/v1/tasks
{
  "title": "Buy groceries",
  "completed": false,
  "due_date": "2025-06-01"
}
```

Task response:
```json
{
  "data": {
    "id": "abc-123",
    "title": "Buy groceries",
    "completed": false,
    "due_date": "2025-06-01"
  }
}
```

Error response:
```json
HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      { "field": "title", "message": "Title is required", "code": "missing_field" }
    ]
  }
}
```

## Notes
- Use **PATCH** for partial updates (e.g., just flipping `completed`); use **PUT** only if clients always send the full resource.
- Plural noun resource (`tasks`), kebab-case, versioned with `/v1/`.
- `completed` defaults to `false` on create if omitted; `due_date` should be an ISO 8601 date.
api-design--m2
baseline · 1.00
# Orders API Pagination & Filtering Design

## Query Parameters

| Parameter | Type | Default | Constraints |
|---|---|---|---|
| `status` | string | *(none = all)* | One of `pending`, `paid`, `shipped` |
| `created_after` | ISO 8601 timestamp | *(none)* | Inclusive lower bound |
| `created_before` | ISO 8601 timestamp | *(none)* | Inclusive upper bound |
| `limit` | integer | `25` | 1–100 (max page size: **100**) |
| `cursor` | string | *(none)* | Opaque cursor from previous response |

**Example request:**

```
GET /orders?status=paid&created_after=2024-01-01T00:00:00Z&created_before=2024-01-31T23:59:59Z&limit=50
```

## Pagination approach: cursor-based (recommended)

Use **keyset pagination** with an opaque cursor derived from the last item's sort key (`created_at` + `id` as a tiebreaker). This is stable under inserts/deletes and performs well at scale — unlike `page`/`offset`, which skips or duplicates rows when data changes between requests.

- Sort orders by `created_at DESC, id DESC` (newest first) — document this as fixed.
- The cursor is a base64-encoded, server-signed token (e.g., `{created_at, id}`); clients must treat it as opaque.
- Return `next_cursor` only when more results exist; `null`/omitted means end of results.

## Response Body

```json
{
  "data": [
    {
      "id": "ord_01HVQ3XK9M",
      "customer_id": "cus_8Zk2P",
      "status": "paid",
      "created_at": "2024-01-15T10:32:00Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNC0wMS0xNVQxMDoxNDowMFoiLCJpZCI6Im9yZF8wMUhWUTNYSEFNIn0",
    "has_more": true,
    "limit": 50
  }
}
```

## Validation & Error Handling

- Invalid `status` → `400` with a message listing allowed values.
- `limit` out of range → `400` (or silently clamp to 100; pick one and document it — explicit 400 is clearer).
- `created_after > created_before` → `400`.
- Unknown/expired cursor → `400` (clients should restart from page 1).
- Invalid date format → `400` with expected format (`ISO 8601 / RFC 3339`).

## Notes

- Keep `limit` rather than `page_size`/`per_page` — it's the most common convention (Stripe, GitHub, etc.).
- If you must support offset pagination for legacy clients, cap `offset` (e.g., 10,000) to protect the database.
- Consider `Link` headers (`rel="next"`) as an alternative to embedding the cursor in the body — either is fine; just be consistent.
with skill · 1.00
# Designing `GET /orders` with Pagination and Filtering

## Query Parameters

| Parameter | Type | Default | Notes |
|---|---|---|---|
| `status` | string | (none = all) | One of `pending`, `paid`, `shipped`. Support comma-separated multiple values: `status=pending,paid`. Reject invalid values with `400`. |
| `created_at[after]` | ISO 8601 datetime | (none) | Inclusive lower bound |
| `created_at[before]` | ISO 8601 datetime | (none) | Inclusive upper bound |
| `customer_id` | string | (none) | Cheap to add now; clients will ask for it |
| `sort` | string | `-created_at` | e.g. `sort=-created_at,id` |
| `limit` | integer | 20 | **Default page size: 20**, **Max page size: 100**. Values >100 → clamp or return `400` (pick one and document it; clamping with a `meta` echo is friendlier). `limit=0` or negative → `400`. |
| `cursor` | string | (none) | Opaque pagination token (see below) |

Example:

```
GET /api/v1/orders?status=paid,shipped&created_at[after]=2025-01-01T00:00:00Z&created_at[before]=2025-01-31T23:59:59Z&limit=20
```

## Cursor vs. offset pagination

- **Cursor-based (recommended):** stable under concurrent inserts, constant performance on large datasets. Cursor encodes the last item's `created_at` + `id` (tiebreaker), so ordering must be deterministic: `ORDER BY created_at DESC, id DESC`.
- Offset (`page`/`per_page`) is fine only if the dataset is small (<10K) or clients need to jump to page N.

## Response Body

```json
{
  "data": [
    {
      "id": "ord_abc123",
      "customer_id": "cus_8812",
      "status": "paid",
      "created_at": "2025-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "has_next": true,
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNS0wMS0xNVQxMDoxNTowMFoiLCJpZCI6Im9yZF94eTk5In0",
    "limit": 20
  },
  "links": {
    "self": "/api/v1/orders?status=paid&limit=20",
    "next": "/api/v1/orders?status=paid&limit=20&cursor=eyJjcmVhdGVkX2F0Ijoi..."
  }
}
```

Key points:

- **`data` / `meta` / `links` envelope** — `links.next` gives clients a copy-pasteable URL; omit `next` (or set `null`) on the last page.
- Fetch `limit + 1` rows to compute `has_next` without a count query. Avoid `meta.total` with cursor pagination — counting on every page is expensive; if clients need totals, offer them via a separate `HEAD`-style count or an offset mode.
- Timestamps in UTC ISO 8601 with `Z`.

## Validation errors

Return `400` with field-level details:

```json
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      { "field": "status", "message": "Must be one of: pending, paid, shipped", "code": "invalid_value" },
      { "field": "limit", "message": "Must be between 1 and 100", "code": "out_of_range" }
    ]
  }
}
```

## Implementation notes

- **Index:** composite index on `(status, created_at DESC, id DESC)` (or `(created_at, id)` if status filtering is rare) so the cursor query is an index scan.
- **Tiebreaker:** always include `id` in the sort/cursor — `created_at` alone can collide.
- **Document limits:** default 20, max 100, and state whether oversized `limit` is clamped or rejected.
api-design--m3
baseline · 0.74
# Consistent JSON Error Format

Use a single error envelope across all endpoints, based on RFC 9457 (Problem Details) style:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "card_declined",
    "message": "Your card was declined.",
    "param": "payment_method",
    "doc_url": "https://api.example.com/docs/errors#card_declined",
    "request_id": "req_8f3k2m9x"
  }
}
```

Key fields:
- **`type`** — broad category: `invalid_request_error`, `authentication_error`, `permission_error`, `not_found`, `rate_limit_error`, `idempotency_error`, `api_error` (our fault / 5xx).
- **`code`** — machine-readable, stable identifier for programmatic handling.
- **`message`** — human-readable, safe to display.
- **`param`** — which field caused the error (for validation).
- **`request_id`** — always include; it's what support will ask for.

Validation errors with multiple failures can add a `errors: [{code, param, message}]` array.

Rules:
- HTTP status codes stay semantically correct (400, 401, 402, 403, 404, 409, 422, 429, 500, 503).
- Never return `200` with an error body.
- Include `request_id` in response headers too (`X-Request-Id`).
- Publish the full error code catalog in docs and treat codes as immutable once shipped.

# Versioning Strategy

**URL-path versioning** is the pragmatic choice for a public API: `/v1/invoices`. It's explicit, cacheable, easy to route, and easy for customers to reason about.

Handling breaking changes:

1. **Never break within a version.** Once `/v1` is public, existing fields, semantics, and error codes are frozen.
2. **Additive changes are fine in-place:** new optional request fields, new response fields (clients should ignore unknown fields — document this), new endpoints, new enum values (with care — document that enums may grow).
3. **Breaking changes require a new version:** removing/renaming fields, changing field types or semantics, changing auth behavior, changing default pagination, removing endpoints.
4. **Deprecation process:**
   - Announce via changelog, email, and status page.
   - Mark deprecated resources in docs; return a `Deprecation` and `Sunset` header on responses using deprecated behavior.
   - Give a long runway (12+ months is common for billing/financial APIs).
   - Track usage per version so you know when it's safe to retire.
5. **Version lifecycle:** only ship a `/v2` when you actually have breaking changes — don't version for additive work. Keep old versions running until usage drops below a threshold, then sunset with notice.

Practical tips for a billing API specifically:
- Use **idempotency keys** on POST /v1/payments — required for payment retries.
- Treat amounts/currency formats as frozen early (e.g., integer minor units) — changing them later is a forced v2.
- Add a `X-API-Version` echo header so customers can verify what they're pinned to.
with skill · 1.00
## Error Response Format

Use one consistent envelope across all endpoints:

```json
HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      { "field": "amount", "message": "Must be a positive number", "code": "out_of_range" },
      { "field": "currency", "message": "Must be a valid ISO 4217 code", "code": "invalid_format" }
    ]
  }
}
```

Key rules:

- **Machine-readable `code`** (snake_case): `not_found`, `validation_error`, `card_declined`, `invoice_already_paid`, `rate_limit_exceeded`, `idempotency_key_reused`. Clients branch on `code`, never on `message` strings.
- **`details` array for field-level errors** — critical for a billing API where a single request can fail validation in multiple places.
- **HTTP status codes carry the semantics** — never return 200 with an error body. Map:
  - `400` malformed JSON / bad request, `401` missing/invalid key, `403` insufficient permissions, `404` unknown resource, `409` state conflicts (e.g., paying an already-paid invoice), `422` semantically invalid data, `429` rate limited (with `Retry-After`), `500` unexpected (never leak internals).
- **Billing-specific extras**: include a `request_id` at the top level for support/debugging, and use idempotency keys on POST /v1/payments — return a distinct `idempotency_error` code on key reuse with a different payload.

## Versioning Strategy

**URL path versioning** (`/v1/...`) is the right choice for a public billing API — explicit, easy to route, cacheable, and trivially testable with curl.

Handle changes like this:

**Non-breaking — no new version needed:**
- Adding new response fields (clients must ignore unknown fields — document this)
- Adding new optional query params or endpoints
- Adding new enum values (document that clients shouldn't exhaustively switch on enums like `invoice.status`)

**Breaking — new version required:**
- Removing/renaming fields, changing field types or formats (e.g., cents → decimal), changing auth, changing URL structure, removing enum values

**Deprecation lifecycle:**
1. Announce 6+ months ahead (email, changelog, status page)
2. Emit a `Deprecation` and `Sunset` header on v1 responses once announced:
   ```
   Sunset: Sat, 01 Jan 2026 00:00:00 GMT
   ```
3. Keep at most 2 active versions (v1 + v2)
4. After sunset, return `410 Gone` with an error body pointing to migration docs

**Practical tip for billing specifically:** money field format changes are the most common breaking change. Decide now whether amounts are integer minor units (cents) or decimal strings, and lock it in for v1 — changing it later forces a v2.
api-design--m4
baseline · 1.00
# API Redesign Recommendations

## 1. Resource-oriented paths (RESTful)

Your current endpoints are RPC-style. Move to resource nouns:

| Current | Proposed |
|---|---|
| `POST /createSubscription` | `POST /v1/subscriptions` |
| `GET /getSubscription?id=123` | `GET /v1/subscriptions/{id}` |
| `POST /cancelSubscription` | `DELETE /v1/subscriptions/{id}` (or `POST /v1/subscriptions/{id}/cancel` if cancel has side effects like proration) |
| `GET /getAllSubscriptions` | `GET /v1/subscriptions` |
| `PUT /updateSubscription` | `PATCH /v1/subscriptions/{id}` (partial updates; keep `PUT` only for full replacement) |

Add `v1` prefix now so future breaking changes ship as `v2`.

## 2. Status codes

- `201 Created` + `Location: /v1/subscriptions/{id}` on create
- `200 OK` on read/update; `204 No Content` on delete/cancel
- `400 Bad Request` — malformed input / validation errors
- `401 Unauthorized` — missing/invalid credentials
- `403 Forbidden` — authenticated but not allowed
- `404 Not Found` — unknown subscription ID
- `409 Conflict` — e.g., canceling an already-canceled subscription
- `422 Unprocessable Entity` — semantically invalid payload (optional; 400 is fine)
- `429 Too Many Requests` — rate limited
- `5xx` — server faults, never leak stack traces

## 3. Error format (JSON, not plain text)

Use a consistent envelope, ideally aligned with RFC 9457 (Problem Details):

```json
{
  "error": {
    "code": "subscription_already_canceled",
    "message": "Subscription sub_123 was already canceled on 2024-05-01.",
    "status": 409,
    "request_id": "req_abc123",
    "details": [
      { "field": "plan_id", "issue": "must be one of: basic, pro, enterprise" }
    ]
  }
}
```

Machine-readable `code` + human `message` + `request_id` for support/debugging.

## 4. Pagination

Cursor-based (stable under writes, better than offset for large datasets):

```
GET /v1/subscriptions?limit=50&cursor=cur_xyz&status=active
```

Response:

```json
{
  "data": [ ... ],
  "pagination": {
    "next_cursor": "cur_abc",
    "has_more": true
  }
}
```

- Default `limit=25`, max `limit=100`
- Support filtering (`status`, `customer_id`) and sorting (`created_at:desc`)

## 5. Rate limiting

- Token-bucket per API key (e.g., 100 req/min sustained, small burst allowance)
- Return headers on every response:

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1718172000
Retry-After: 12   (on 429 only)
```

- Return `429` with the standard error envelope when exceeded
- Consider stricter tiers for expensive endpoints (e.g., list/export)

## 6. Other quick wins

- **Idempotency**: accept an `Idempotency-Key` header on `POST /v1/subscriptions` so retries don't create duplicates
- **Versioning**: `v1` in path (or `Accept` header)
- **Consistent IDs**: prefixed opaque IDs like `sub_1a2b3c` instead of bare integers
- **Timestamps**: ISO 8601 UTC everywhere
- **OpenAPI spec**: publish it, generate docs and client SDKs from it
- **Webhooks**: notify callers of subscription lifecycle events (created, renewed, canceled) instead of forcing polling
with skill · 0.50
## Redesigned API

Your current design uses verbs in URLs (`/createSubscription`), hides resource IDs in query params or request bodies, and returns untyped plain-text errors. Here's a concrete REST redesign.

### Endpoint paths

```
POST   /api/v1/subscriptions                 # create
GET    /api/v1/subscriptions/{id}            # retrieve one
PATCH  /api/v1/subscriptions/{id}            # partial update (plan, add-ons)
DELETE /api/v1/subscriptions/{id}            # hard delete (rare — admin only)
POST   /api/v1/subscriptions/{id}/cancel     # state change (soft cancel)
POST   /api/v1/subscriptions/{id}/reactivate # undo cancel within grace period
GET    /api/v1/subscriptions                 # list, with filters
GET    /api/v1/customers/{customer_id}/subscriptions  # subscriptions for a customer
```

Key changes:
- **Nouns, plural, kebab-case** — `/subscriptions`, not `/subscription` or `/createSubscription`.
- **ID in the path** — `/subscriptions/123`, not `?id=123`. This makes every resource addressable and cacheable.
- **`PATCH` instead of `PUT`** for updates — clients send only changed fields; `PUT` requires the full resource and silently wipes fields clients omit.
- **`cancel` as a sub-resource action** — cancellation is a state transition, not CRUD, so a verb action under the resource is the right pattern. It also makes the target explicit (your current `POST /cancelSubscription` doesn't say *which* subscription).

### Status codes per endpoint

| Endpoint | Success | Errors |
|---|---|---|
| `POST /subscriptions` | `201 Created` + `Location: /api/v1/subscriptions/123` | `400` malformed JSON, `422` validation, `409` duplicate active subscription |
| `GET /subscriptions/{id}` | `200 OK` | `404` not found, `403` not yours |
| `PATCH /subscriptions/{id}` | `200 OK` (return updated resource) | `404`, `422`, `409` already cancelled |
| `POST /subscriptions/{id}/cancel` | `200 OK` (return cancelled resource) | `404`, `409` already cancelled |
| `POST /subscriptions/{id}/reactivate` | `200 OK` | `404`, `409` outside grace period |
| `DELETE /subscriptions/{id}` | `204 No Content` | `404`, `403` |
| `GET /subscriptions` | `200 OK` | `400` invalid filter/sort param |

Rules to enforce: never return `200` with an error in the body; use `422` for semantically invalid data (valid JSON, bad values) and `400` for malformed requests; `401` for missing/invalid auth, `403` for authenticated-but-not-authorized.

### Error format (replace plain text)

Structured JSON, machine-readable `code`, stable across endpoints:

```json
HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      { "field": "plan_id", "message": "Unknown plan", "code": "invalid_reference" },
      { "field": "billing_cycle", "message": "Must be one of: monthly, annual", "code": "invalid_value" }
    ]
  }
}
```

Standard error codes to define: `validation_error`, `not_found`, `forbidden`, `unauthorized`, `conflict`, `already_cancelled`, `rate_limit_exceeded`, `internal_error`. Log the internal cause server-side; never leak stack traces in the response.

### Pagination for `GET /subscriptions`

Use **cursor-based** pagination — subscription lists grow unboundedly and offset pagination degrades and skips rows under concurrent inserts:

```
GET /api/v1/subscriptions?limit=20&cursor=eyJpZCI6MTIzfQ&status=active&sort=-created_at
```

```json
api-design--m5
baseline · 1.00
# Library Management REST API

## Endpoints

| Method | Endpoint | Description | Success Status | Error Statuses |
|--------|----------|-------------|----------------|----------------|
| POST | `/books` | Create a book | `201 Created` | `400 Bad Request` (invalid body), `409 Conflict` (duplicate ISBN) |
| GET | `/books` | List books (filters: `author`, `available`) | `200 OK` | — |
| GET | `/books/{id}` | Get one book | `200 OK` | `404 Not Found` |
| PUT | `/books/{id}` | Replace a book | `200 OK` | `400 Bad Request`, `404 Not Found` |
| PATCH | `/books/{id}` | Partially update (e.g., toggle `available`) | `200 OK` | `400 Bad Request`, `404 Not Found` |
| DELETE | `/books/{id}` | Delete a book | `204 No Content` | `404 Not Found` |

## Book Resource

```json
{
  "id": 1,
  "title": "The Hobbit",
  "author": "J.R.R. Tolkien",
  "isbn": "978-0547928227",
  "published_year": 1937,
  "available": true
}
```

## Example: List Books

**Request**

```
GET /books?author=Tolkien&available=true
```

**Response — 200 OK**

```json
{
  "books": [
    {
      "id": 1,
      "title": "The Hobbit",
      "author": "J.R.R. Tolkien",
      "isbn": "978-0547928227",
      "published_year": 1937,
      "available": true
    },
    {
      "id": 2,
      "title": "The Fellowship of the Ring",
      "author": "J.R.R. Tolkien",
      "isbn": "978-0547928210",
      "published_year": 1954,
      "available": true
    }
  ],
  "total": 2
}
```

Notes: filters are optional and combinable; no matches returns `200 OK` with an empty list.
with skill · 0.70
# Library Books REST API

Base path: `/api/v1/books`

## Endpoints

| Method | Path | Description | Status Codes |
|--------|------|-------------|--------------|
| GET | `/api/v1/books` | List books. Filters: `?author=...&available=true` | 200, 400 (invalid filter value) |
| GET | `/api/v1/books/:id` | Get one book | 200, 404 |
| POST | `/api/v1/books` | Create a book | 201 (with `Location` header), 400/422 (validation), 409 (duplicate ISBN) |
| PUT | `/api/v1/books/:id` | Full update | 200, 400/422, 404 |
| PATCH | `/api/v1/books/:id` | Partial update (e.g., toggle `available`) | 200, 400/422, 404 |
| DELETE | `/api/v1/books/:id` | Delete a book | 204, 404 |

## Example: List books filtered by author and availability

**Request**

```
GET /api/v1/books?author=Tolkien&available=true
```

**Response**

```
HTTP/1.1 200 OK
```

```json
{
  "data": [
    {
      "id": 12,
      "title": "The Hobbit",
      "author": "J.R.R. Tolkien",
      "isbn": "978-0547928227",
      "published_year": 1937,
      "available": true
    }
  ],
  "meta": {
    "total": 1,
    "page": 1,
    "per_page": 20
  }
}
```

Notes:
- Filters combine with AND semantics; unknown query params return 400.
- Errors use a consistent shape: `{ "error": { "code": "not_found", "message": "Book not found" } }`.
Limitations
  • Agent noise: single-model results vary run to run; the confidence interval reflects task-level variance.
  • Screen tier (1 trial/arm) is a directional gate, not a verified claim.
  • Results are for the SkillzBench Reference Agent on glm-5.3-flash@cf and do not necessarily transfer to other agents or models.
  • Task coverage: Skill-matched tasks (generated, public + held-out) only; a skill can shine elsewhere.