Skip to main content

API Naming Conventions & Style Guide

Naming is the cheapest-to-get-right, most-expensive-to-fix part of an API. A bad name is permanent. Pick a documented style guide, encode it as Spectral rules, and never debate it in code review again.

Searchable Rule Book

API Naming Convention Lookup

21 rules
URLPlural nouns for collections
βœ” /orders/{orderId}✘ /getOrder, /order/{id}

The HTTP method is the verb; collections are plural so /orders and /orders/42 are consistent.

URLkebab-case path segments
βœ” /shipping-addresses✘ /shippingAddresses, /shipping_addresses

URLs are case-sensitive in the path; lowercase hyphenated is readable and SEO/CDN-safe.

URLMax two levels of nesting
βœ” /orders/42/items, /items?orderId=42✘ /customers/1/orders/42/items/7/prices

Deep nesting couples URLs to hierarchy and breaks when ownership changes; expose sub-resources by top-level id.

URLNo verbs, no file extensions, no trailing slash
βœ” /invoices/42✘ /invoices/42.json/, /createInvoice

Use Accept for format and methods for actions.

URLCustom actions as sub-resource or colon verb
βœ” POST /orders/42:cancel or POST /orders/42/cancellation✘ GET /cancelOrder?id=42

Non-CRUD state transitions need POST; the colon style follows Google AIP-136.

QuerycamelCase or snake_case, pick ONE
βœ” pageSize, sortBy✘ page_size & sortBy mixed

Consistency matters more than the choice; enforce with a linter.

QueryPagination params
βœ” limit + cursor (or pageSize + pageToken)✘ p, n, start, rows

Opaque cursor tokens let you change storage without breaking clients.

QuerySorting with a signed field list
βœ” sort=-createdAt,name✘ orderBy=createdAt DESC

Leading minus = descending; never pass raw SQL fragments (injection risk).

QueryFiltering uses explicit field names
βœ” status=PAID&createdAfter=2025-01-01T00:00:00Z✘ filter=status eq paid (ad-hoc)

Use a documented grammar (RSQL/OData) only if you truly need it; otherwise simple fields.

JSONcamelCase property names
βœ” firstName, createdAt✘ first_name, FirstName

Matches JavaScript and most client SDKs; snake_case is acceptable if used everywhere (Stripe, GitHub).

JSONISO-8601 / RFC 3339 timestamps in UTC
βœ” "2025-03-01T10:15:30Z"✘ 1709287530, "03/01/2025"

Unambiguous, sortable, timezone-safe. Date-only fields use YYYY-MM-DD.

JSONMoney as string/integer minor units + currency
βœ” {"amount":"1999","currency":"USD"}✘ {"price":19.99}

Binary floating point cannot represent decimals; JavaScript numbers lose precision above 2^53.

JSONBooleans are positive adjectives/verbs
βœ” isActive, hasChildren✘ notDisabled, active_flag

Double negatives are error-prone.

JSONEnums UPPER_SNAKE_CASE strings
βœ” "status":"PENDING_PAYMENT"✘ "status":2

Self-describing and stable; clients must tolerate unknown values (open enums).

JSONCollections are plural, ids are strings
βœ” "items":[...], "id":"ord_8f3k"✘ "item":[...], "id":42 (leaks counts)

Opaque string ids prevent enumeration and allow migration to UUID/ULID.

HeadersStandard headers over custom X- headers
βœ” Idempotency-Key, Retry-After, Sunset✘ X-Idempotency, X-Retry

RFC 6648 deprecated the X- prefix; use registered names or an org prefix.

HeadersCorrelation / trace id
βœ” traceparent (W3C Trace Context)✘ X-Req-Id invented per team

Interoperates with OpenTelemetry, gateways, and vendors.

OpenAPIoperationId is unique camelCase verbNoun
βœ” listOrders, getOrderById, cancelOrder✘ get_orders_orders_get (auto)

Becomes the method name in generated SDKs; instability renames client methods.

OpenAPISchema names PascalCase, role-suffixed
βœ” Order, CreateOrderRequest, OrderResponse, ProblemDetail✘ order_dto2, Order1

Generated class names; separate request/response to avoid readOnly/writeOnly confusion.

OpenAPITags group by resource, summary < 60 chars
βœ” tags: [Orders] summary: "List orders"✘ tags: [OrderController]

Tags drive navigation and SDK grouping, not Java class names.

OpenAPIReuse via components + $ref
βœ” $ref: "#/components/responses/NotFound"✘ Copy/pasted inline error schemas

One definition = one change; prevents drift.

1. URL Design

RuleExampleRationale
Nouns, not verbsGET /orders not GET /getOrdersHTTP method carries the verb
Plural collections/orders, /orders/ord_42Uniform shape for list vs item
kebab-case/shipping-addressesPath is case-sensitive; hyphen readable
Lowercase only/ordersAvoid /Orders vs /orders duplicates and cache misses
No trailing slash, no extension/orders/42One canonical URL per resource
Identify with stable opaque ids/orders/ord_8f3kd92kDo not leak sequence/count; prevents enumeration
Limit nesting to 2 levels/orders/42/itemsDeep paths couple clients to hierarchy
Sub-resources only for true composition/orders/42/items ok; /customers/7/orders vs /orders?customerId=7 - prefer the filterA resource should have ONE canonical location
Singleton sub-resource singular/users/me/profile, /orders/42/cancellationExactly one per parent
Version in a single, consistent place/v1/... or headerSee Versioning

Non-CRUD Operations (Actions)

REST models state, but real systems have commands. Options, in order of preference:

  1. Model the action as a resource - POST /orders/42/cancellations creates a cancellation (auditable, retrievable, idempotent by key).
  2. Colon custom method (Google AIP-136) - POST /orders/42:cancel. Compact, clearly not CRUD.
  3. State field update - PATCH /orders/42 with {"status":"CANCELLED"} only when transitions are trivial and validated.
  4. Controller sub-resource verb - POST /orders/42/cancel acceptable if consistent; never GET for state changes.

2. Query Parameters

ConcernConventionExample
CaseSame as JSON fields (camelCase)pageSize, createdAfter
PaginationCursor: limit, cursor; or pageSize, pageToken (AIP-158)?limit=25&cursor=eyJpZCI6...
SortingComma list, - prefix for desc?sort=-createdAt,name
FilteringField name = value; range via suffix/prefix?status=PAID&createdAfter=...&createdBefore=...
Multi-valueRepeat or comma (document one)?status=PAID&status=SHIPPED
Sparse fieldsfields?fields=id,status,total
Expansionexpand?expand=customer,items.product
Searchq?q=wireless+headphones
Booleanstrue/false literals?includeDeleted=true

Reserve these names across every API in the company so SDKs and gateways can treat them uniformly.

3. JSON Property Naming

RuleGoodAvoid
Choose ONE case org-wide (camelCase default; snake_case if you are Stripe/GitHub-style)firstNameMixed first_name and lastName
Boolean as predicateisActive, hasChildren, canEditactive, flag, notDisabled
Timestamps end in At, dates in OncreatedAt, dueOncreated, date1
Durations include unit or use ISO-8601timeoutSeconds, "PT15M"timeout: 30
Arrays pluralitems, tagsitem, tagList
Foreign keys end in IdcustomerIdcustomer (ambiguous id vs object)
Embedded object vs idcustomer: {id, name} when expandedChanging the type of a field between calls
Money{ "amount": "1999", "currency": "USD" } or integer minor units19.99 float
No abbreviations unless universalquantity, url, idqty, desc, addr1
No redundant prefixorder.statusorder.orderStatus
Null vs absent documentedabsent = unchanged (PATCH), null = clearTreating them identically

Identifiers

StyleExampleNotes
Prefixed randomord_8f3kd92k (Stripe)Self-describing in logs; safe to expose
UUIDv43f2b...Opaque; poor B-tree locality as PK
UUIDv7 / ULID01HV...Time-ordered: better index locality, leaks creation time
Auto-increment12345Never expose: enumeration, count leaks, BOLA targets

Ids are strings in JSON even if numeric internally (JavaScript loses precision above 2^53).

Enums

  • Strings in UPPER_SNAKE_CASE: PENDING_PAYMENT (or lowercase pending_payment consistently).
  • Open enum rule: clients must treat unknown values as "other" - adding a value must not be a breaking change. Declare this in the description.
  • Never use ordinal numbers; they cannot be reordered or reused.

4. HTTP Headers

PurposeUseDon't
IdempotencyIdempotency-Key (IETF draft)X-Idempotency-Token
Tracingtraceparent, tracestate (W3C)X-Request-Trace-Id-V2
Rate limitsRateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, Retry-AfterBespoke names per team
DeprecationDeprecation, Sunset (RFC 8594), Link: rel="successor-version"Docs-only notice
PaginationLink header (RFC 8288) or body nextCursorUndocumented X-Next
ConcurrencyETag, If-Match, If-None-Matchversion query param
Custom headersOrg prefix without X- (RFC 6648), e.g. Acme-Tenant-IdX- prefix for new headers

Header names are case-insensitive; docs use Title-Case.

5. OpenAPI Document Naming

ElementConventionExample
operationIdcamelCase verbNoun, unique, stablelistOrders, getOrderById, cancelOrder
TagsResource names, PascalCase/TitleOrders, Shipping Addresses
SchemasPascalCase nouns with role suffixOrder, CreateOrderRequest, UpdateOrderRequest, OrderPage, ProblemDetail
Parameters (components)PascalCaseLimit, Cursor, IdempotencyKey
Responses (components)HTTP semantic namesNotFound, Unauthorized, ValidationError
Security schemescamelCasebearerAuth, oauth2
Filesopenapi.yaml, split by domain: orders.openapi.yamlKeep one spec per deployable API

operationId verbs: list (collection GET), get (single), create (POST), replace (PUT), update (PATCH), delete (DELETE), then domain verbs (cancel, capture, refund).

6. Style Guide Comparison

TopicGoogle AIPMicrosoft RESTZalandoStripe
Path caselowerCamelCase collection ids (userEvents)camelCase/kebabkebab-casesnake_case (payment_intents)
Property casecamelCase (JSON)camelCasesnake_casesnake_case
Custom methodsPOST /v1/resources/id:verbPOST .../actionsub-resourcePOST /v1/charges/id/capture
PaginationpageSize, pageToken$top, $skip, nextLinkcursor limit/cursorlimit, starting_after
Errorsgoogle.rpc.Statuserror{code,message}RFC 7807 problemerror{type,code,message,param}
VersioningMajor in path, stablePath or queryMedia-type versioningDate-based header Stripe-Version
EnumsUPPER_SNAKEPascalCase stringlower_snakelowercase_snake

There is no universal winner; copy a mature guide, then diverge in writing, enforcing it with Spectral.

7. Spectral Ruleset Example

# .spectral.yaml
extends: ["spectral:oas"]
rules:
paths-kebab-case:
description: Path segments must be kebab-case
severity: error
given: $.paths[*]~
then:
function: pattern
functionOptions:
match: "^(/([a-z0-9]+(-[a-z0-9]+)*|\\{[a-zA-Z0-9]+\\}))+$"
property-camel-case:
description: Schema properties must be camelCase
severity: error
given: $..properties[*]~
then:
function: casing
functionOptions: { type: camel }
operation-id-camel-case:
severity: error
given: $.paths[*][*].operationId
then:
function: casing
functionOptions: { type: camel }
error-responses-problem-json:
description: 4xx/5xx must use application/problem+json
severity: warn
given: $.paths[*][*].responses[?(@property.match(/^[45]/))].content
then:
field: application/problem+json
function: truthy

8. Naming Review Checklist

  • Every path is a plural noun in kebab-case with at most two levels
  • No verbs in paths except documented custom actions
  • All property names follow one case; booleans are predicates; timestamps end with At
  • Ids are opaque strings; enums are strings and open
  • Money, durations, and dates use unambiguous formats
  • operationIds are unique, stable, and verb-first
  • Standard parameter names (limit, cursor, sort, fields, expand) reused
  • Headers use standard names or an org prefix (no X-)

Next: Resource Modeling & HTTP Semantics.

πŸ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%