The definitive guide to API resource naming conventions

Clear rules for naming API resources, fields, and events across REST, GraphQL, and gRPC—with examples, pitfalls, and a practical checklist.

ASOasis
6 min read
The definitive guide to API resource naming conventions

Image used for representation purposes only.

Why naming conventions matter

Good names reduce cognitive load, prevent breaking changes, and make APIs self-documenting. Consistent resource names help clients guess endpoints, discover capabilities, and avoid subtle bugs that come from case, pluralization, or action verbs used inconsistently. This guide distills practical conventions you can adopt across REST, GraphQL, gRPC, and evented APIs.

Core principles

  • Prefer nouns over verbs for resources; use HTTP methods (or RPC methods) for actions.
  • Be consistent: pick a style (case, separators, versioning) and stick to it everywhere.
  • Optimize for readability over keystrokes; avoid cryptic abbreviations.
  • Stability beats cleverness; changing names is a breaking change.
  • Names are part of your contract—document them, lint them, and test them.

REST resource paths

Collections and items

  • Collections: plural, kebab-case, lowercase.
  • Items: append an opaque identifier segment.

Examples:

GET  /users
GET  /users/{user_id}
POST /users
GET  /orders/{order_id}/items

Bad patterns to avoid:

GET /getUsers               # verbs in paths
GET /user/123               # singular collection name
GET /Orders                 # inconsistent case

Hierarchies and relationships

  • Use hierarchical paths for strong ownership: orders/{order_id}/items.
  • Use top-level collections with filter params for weak relationships: GET /items?order_id=....
  • Keep path parameter names singular and explicit: {user_id}, {order_id}.
  • Don’t repeat the parent name in child segments: prefer /users/{user_id}/sessions/{session_id} over /users/{user_id}/user-sessions/{session_id}.

Case and separators

  • Paths: kebab-case (hyphens) for readability: /credit-cards.
  • Query parameters: choose snake_case or camelCase; snake_case works well in URLs and many backends.
  • JSON fields: camelCase is common (interops with JS), but snake_case is fine—just be consistent.

Trailing slashes and file extensions

  • No trailing slash on canonical URLs: /users not /users/.
  • No file extensions: prefer Accept/Content-Type headers over /users.json.

Identifiers

  • Use opaque, stable IDs (UUIDs, ULIDs, snowflakes). Don’t encode meaning in IDs.
  • Field names: id for the resource’s primary key; foreign keys use suffixed names (e.g., userId or user_id).
  • Timestamp fields: createdAt, updatedAt (or created_at, updated_at), RFC 3339 strings.

Actions and operations

  • Use HTTP methods for CRUD:
    • GET (safe, idempotent) read
    • POST (not idempotent) create or trigger a server-side action that changes state
    • PUT/PATCH (idempotent/partial) update
    • DELETE (idempotent) delete
  • For non-CRUD domain actions, model them as sub-resources or action endpoints:
POST /payments/{payment_id}/capture
POST /payments/{payment_id}/refunds

If you need to emphasize action semantics, a colon action is a clean, explicit option inspired by Google APIs:

POST /payments/{payment_id}:capture

Prefer idempotency keys for actions that could be retried:

curl -X POST \
  -H 'Idempotency-Key: 2ca9a4e7-...' \
  'https://api.example.com/payments/{payment_id}/refunds'

Examples: a cohesive resource tree

/v1
  /users
    GET /users
    POST /users
    GET /users/{user_id}
    PATCH /users/{user_id}
    /users/{user_id}/sessions
    /users/{user_id}/roles
  /orders
    GET /orders?status=pending&customer_id=...
    GET /orders/{order_id}
    /orders/{order_id}/items
    POST /orders/{order_id}/cancel

Query parameters

Establish a small, predictable vocabulary.

  • Filtering: explicit field names
    • GET /orders?status=pending&customer_id=abc123
    • For multi-value: ?status=pending,confirmed or repeated: ?status=pending&status=confirmed (pick one convention).
  • Search: q for free-text search; query for structured queries.
  • Pagination:
    • Offset: ?limit=50&offset=100
    • Page: ?page=3&per_page=50
    • Cursor: ?limit=50&cursor=eyJv... with response fields next_cursor, prev_cursor.
  • Sorting: ?sort=created_at ascending; prefix with - for descending: ?sort=-created_at.
  • Sparse fieldsets: ?fields=id,name,created_at.
  • Embedding/expansion: pick one term—include or expand—e.g., ?include=customer,items.product.

JSON payloads and schemas

  • Case: choose camelCase or snake_case; use it everywhere (requests, responses, errors, webhooks).
  • Booleans: prefix with is/has if it clarifies intent (e.g., isArchived, hasTrial).
  • Money: store as integer minor units (amount: 1299, currency: “USD”).
  • Enums: use stable, lowercase strings with hyphens or snake_case (e.g., status: "pending" | "in-transit" | "delivered").
  • Time: RFC 3339 UTC strings (e.g., "2026-04-04T16:20:00Z").
  • Nullability: prefer omitting absent optional fields over null, unless null is semantically meaningful.

Example response:

{
  "id": "ord_7WQqfQfV",
  "status": "pending",
  "createdAt": "2026-04-04T16:20:00Z",
  "customerId": "cus_kT52z1",
  "amount": 1299,
  "currency": "USD",
  "items": [
    { "productId": "prod_123", "quantity": 2 }
  ],
  "links": { "self": "/v1/orders/ord_7WQqfQfV" }
}

Error representation

  • Top-level error or errors array.
  • Machine-readable code and human-readable message.
  • Include field for validation issues and a requestId for support.
{
  "error": {
    "code": "invalid_argument",
    "message": "amount must be >= 0",
    "field": "amount",
    "requestId": "req_9a5..."
  }
}

Name error codes in snake_case or kebab-case and keep them stable: not_found, permission_denied, rate_limited.

Versioning and names

  • URI versioning: /v1/... keeps names stable within a version.
  • Header/media-type versioning: Accept: application/vnd.acme.v2+json if you need many variants.
  • Avoid embedding version numbers in field names (createdAtV2); add fields with new names and deprecate old ones.

GraphQL naming conventions

  • Types, Inputs, Enums: PascalCase (e.g., User, CreateOrderInput).
  • Fields and arguments: camelCase (e.g., createdAt, orderId).
  • Enum values: UPPER_CASE_SNAKE (e.g., PENDING, IN_TRANSIT).
  • Queries use nouns; mutations are verbs that return changed resources.

Example:

type Order {
  id: ID!
  status: OrderStatus!
  createdAt: DateTime!
  customer: Customer!
  items: [OrderItem!]!
}

enum OrderStatus { PENDING IN_TRANSIT DELIVERED CANCELED }

type Query {
  order(id: ID!): Order
  orders(status: OrderStatus, after: String, first: Int = 50): OrderConnection!
}

type Mutation {
  createOrder(input: CreateOrderInput!): Order!
  cancelOrder(id: ID!): Order!
}

gRPC/Protobuf naming conventions

  • Service and message names: PascalCase (OrderService, CreateOrderRequest).
  • Field names: snake_case by Protobuf convention.
  • RPC methods: PascalCase verbs (CreateOrder, GetOrder, ListOrders).
  • If you expose HTTP via transcoding, keep RESTful path naming consistent with your REST guidelines.
service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (Order) {}
  rpc GetOrder(GetOrderRequest) returns (Order) {}
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse) {}
}

message Order {
  string id = 1;
  string status = 2; // "pending", "in_transit", ...
  string created_at = 3;
}

Event and webhook names

Evented systems benefit enormously from clear, stable names.

  • Use dotted, namespaced, past-tense event names: acme.order.created, acme.order.canceled.
  • Keep payloads aligned with your REST/JSON naming style.
  • Include a type, id, occurredAt, and data envelope.
{
  "id": "evt_1G...",
  "type": "acme.order.created",
  "occurredAt": "2026-04-04T16:20:00Z",
  "data": {
    "id": "ord_7WQqfQfV",
    "status": "pending",
    "customerId": "cus_kT52z1"
  }
}

Topic naming for streams (Kafka, SNS, NATS):

  • Use product and domain namespaces: acme.orders.v1.events.
  • Partition key name: match your business key (e.g., order_id).

Internationalization, domains, and tenants

  • Resource names are part of the API grammar—keep them in English.
  • Localize data, not endpoints; respect Accept-Language for fields that carry human text.
  • For multi-tenant APIs, make tenancy explicit via headers or a stable path segment: /v1/{tenant_id}/users or X-Tenant-Id: tnt_123 (pick one and standardize).

Consistency across SDKs

  • Mirror server naming in SDKs, but follow language idioms for casing:
    • Java/Kotlin: camelCase methods/fields, PascalCase types.
    • Python/Ruby: snake_case methods/fields, PascalCase classes.
    • JS/TS: camelCase methods/fields, PascalCase classes.
  • Keep method verbs aligned with API actions: client.orders.create(...), client.orders.cancel(order_id).

A minimal style guide you can adopt today

  • Paths: lowercase, kebab-case, plural nouns; no trailing slashes.
  • IDs: opaque strings; id for self, {resource}Id for references.
  • Query: limit, offset or page, per_page; cursor for cursor-based.
  • Sorting: sort=field or sort=-field.
  • Timestamps: RFC 3339 UTC strings named createdAt, updatedAt.
  • Errors: snake_case code, human message, optional field, requestId.
  • Versions: /v1, evolve with additive changes; avoid renaming in-place.
  • Events: vendor.domain.event in past tense.

Common pitfalls (and how to avoid them)

  • Mixing cases in one API: define a single casing policy for paths, query, and JSON.
  • Encoding actions as verbs in paths: let methods do the work; use sub-resources for domain actions.
  • Overloading GET for destructive operations: never do this; respect HTTP semantics.
  • Leaking database structure: tbl_user in paths or integer autoincrement IDs; prefer opaque business IDs.
  • Inconsistent plurals and irregular nouns: standardize exceptions (people vs persons—pick one and document it).
  • Hidden side effects behind idempotent methods: if it changes state and isn’t idempotent, it’s POST.
  • Ad hoc query parameter names: maintain a dictionary for filters, pagination, and expansion and lint PRs against it.

Design-time checklist

  • Is the resource a noun and pluralized for collections?
  • Are path segments lowercase kebab-case and free of trailing slashes?
  • Do IDs, timestamps, and money fields follow consistent names and formats?
  • Are non-CRUD operations modeled as sub-resources or explicit actions?
  • Are filtering, pagination, and sorting parameters named consistently?
  • Are error codes stable, documented, and machine-readable?
  • Do events/webhooks use a consistent namespace and past-tense names?
  • Is versioning strategy explicit and documented?

Conclusion

Naming is one of the highest-leverage choices in API design. Choose a small set of clear, consistent conventions and enforce them with linters, style guides, and review checklists. Your future users—and your future self—will thank you.

Related Posts