api-design — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited api-design (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ask:
Apply these naming conventions:
| Rule | Good | Bad |
|---|---|---|
| Plural nouns | /users | /user, /getUsers |
| Nested resources | /users/{id}/orders | /getUserOrders |
| Lowercase with hyphens | /order-items | /orderItems, /order_items |
| No verbs in URLs | /users/{id}/activate (POST) | /activateUser |
| Max 3 levels deep | /users/{id}/orders | /users/{id}/orders/{id}/items/{id}/reviews |
For deeply nested resources, promote to top-level with query filters:
GET /reviews?order_id=123&user_id=456Map operations to methods:
| Operation | Method | Success Code | Response Body |
|---|---|---|---|
| List/Search | GET | 200 | Collection |
| Get single | GET | 200 | Resource |
| Create | POST | 201 | Created resource + Location header |
| Full update | PUT | 200 | Updated resource |
| Partial update | PATCH | 200 | Updated resource |
| Delete | DELETE | 204 | Empty |
| Async operation | POST | 202 | Job status + Location header |
Error codes to use consistently:
Pagination (cursor-based preferred for large datasets):
GET /orders?cursor=eyJpZCI6MTAwfQ&limit=25
Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTI1fQ",
"has_more": true
}
}Offset pagination (simpler, fine for small datasets):
GET /orders?page=2&per_page=25
Response:
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 25,
"total": 142,
"total_pages": 6
}
}Filtering and sorting:
GET /orders?status=pending&created_after=2024-01-01&sort=-created_at,+total- for descending, + for ascendingUse a consistent error envelope:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request body contains invalid fields",
"details": [
{
"field": "email",
"issue": "must be a valid email address",
"value": "not-an-email"
}
],
"request_id": "req_abc123",
"documentation_url": "https://api.example.com/docs/errors#VALIDATION_FAILED"
}
}Rules:
code (UPPER_SNAKE_CASE)messagedetails for validation errorsrequest_id for debuggingProduce an OpenAPI 3.0 specification:
openapi: 3.0.3
info:
title: [Service Name] API
version: 1.0.0
description: [Brief description]
paths:
/resources:
get:
summary: List resources
operationId: listResources
parameters:
- name: limit
in: query
schema:
type: integer
default: 25
maximum: 100
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ResourceList'
components:
schemas:
Resource:
type: object
required: [id, name]
properties:
id:
type: string
format: uuid
name:
type: stringRecommend URL-path versioning for most cases:
/v1/users
/v2/usersWhen to create a new version:
When NOT to version (additive changes):
When the consumer needs flexible queries or the API serves multiple clients with different data needs, offer a GraphQL alternative:
Schema definition:
type Query {
book(id: ID!): Book
books(filter: BookFilter, first: Int = 25, after: String): BookConnection!
}
type Mutation {
createBook(input: CreateBookInput!): BookPayload!
updateBook(id: ID!, input: UpdateBookInput!): BookPayload!
deleteBook(id: ID!): DeletePayload!
}
type Book {
id: ID!
title: String!
author: Author!
publishedAt: DateTime
isbn: String
}
input BookFilter {
title: String
authorId: ID
publishedAfter: DateTime
}
input CreateBookInput {
title: String!
authorId: ID!
isbn: String
}
type BookConnection {
edges: [BookEdge!]!
pageInfo: PageInfo!
}
type BookEdge {
node: Book!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
type BookPayload {
book: Book
errors: [UserError!]!
}
type UserError {
field: [String!]
message: String!
}GraphQL design rules:
! only when truly always presentWhen to choose GraphQL over REST:
When to prefer REST:
User says: "Design an API for a bookstore"
Response includes:
GET /v1/books, POST /v1/orders, etc.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.