API Naming Conventions for Paths, Fields, and Headers

API naming conventions with examples: lowercase plural paths, path parameter names, one casing for JSON fields and query params, and HTTP header names.

On this page

Good API naming conventions fit on one card. Paths are lowercase, plural nouns with hyphens between words (/v1/user-profiles), and the HTTP method carries the action, so no verbs in the path. Path parameters get short, unique names such as {id} or {orderId}. JSON fields use one casing across the whole API, either camelCase or snake_case, and query parameters use the same casing as the fields they filter. Booleans start with is or has, timestamps end in At and carry a timezone, and numbers with units say the unit. New HTTP headers drop the X- prefix. The rest of this page explains each rule, the exceptions, and how to rename things without breaking clients.

Naming cheat sheet

ElementConventionGoodAvoid
Collection pathLowercase plural noun/v1/orders/v1/Order, /v1/getOrders
Multi-word segmentkebab-case/v1/user-profiles/v1/userProfiles, /v1/user_profiles
Single resourceCollection plus ID/v1/orders/{id}/v1/order/{id}, /v1/orders/get/{id}
Non-CRUD actionSub-resource nounPOST /v1/orders/{id}/cancellationPOST /v1/cancelOrder
JSON fieldOne casing everywherecustomerId or customer_idBoth in one API
Boolean fieldis or has prefixisActive, hasMoreactive, more
Timestamp fieldAt suffix, ISO 8601 with offsetcreatedAtcreated, creationDate
Value with a unitUnit in the nametimeoutSeconds, totalCentstimeout, total
Enum valueOne casing for all enumsin_progressInProgress next to on-hold
Query parameterSame casing as JSON fields?createdAfter=?created_after= in a camelCase API
New HTTP headerHyphenated words, no X- prefixRequest-IdX_Request_ID, requestid
Pick the casing column once per API and apply it everywhere.

Paths: lowercase, plural, no verbs

RFC 3986 treats the scheme and host of a URI as case-insensitive but the path as case-sensitive, so /v1/Orders and /v1/orders are two different URLs. Some servers and frameworks match routes case-insensitively and others do not, and caches and gateways key on the exact string. Lowercase-only paths remove the question. Between words, use hyphens. Hyphens are the common choice in URLs, they read clearly when a link is underlined, and they keep the path visually separate from snake_case or camelCase JSON.

Name collections with plural nouns and address one item by appending its ID: /v1/orders and /v1/orders/{id}. Mixing /user and /users in the same API forces client developers to look up each path. The HTTP method already says what happens: GET reads, POST creates, PATCH updates part of a resource, DELETE removes it. A path such as /v1/orders/create repeats the method and invites the inconsistency of POST /create next to PUT /update. The REST API design guide covers method semantics in more depth.

Some operations are not create, read, update or delete. Cancelling an order, sending an invoice or resending a verification email are actions. Model them as a sub-resource noun that you create: POST /v1/orders/{id}/cancellation or POST /v1/invoices/{id}/delivery. That keeps the path a noun, gives you a place to return the cancellation record, and works with the same tooling as every other POST.

Keep nesting to one level of ownership. /v1/users/{userId}/orders reads well. /v1/users/{userId}/orders/{orderId}/line-items/{lineItemId} makes the client know the user ID to read a line item, even though the order ID already identifies it. If a child has a globally unique ID, give it a top-level path such as /v1/line-items/{id} and offer filtering by parent with a query parameter.

Path parameter names

OpenAPI identifies a parameter by its name and location together, and every template expression in a path must match a declared path parameter. Two path parameters in one path therefore need different names, which is why nested routes use {userId} and {orderId} instead of {id} twice. The spec also forbids templated paths that differ only in the parameter name: /orders/{id} and /orders/{orderId} in the same document count as the same path and make the document invalid.

For a single parameter, {id} is enough. Use the same casing for parameter names as for your JSON fields, so {orderId} in a camelCase API and {order_id} in a snake_case one. One thing to know if you track the APIForge score: its naming check reads the whole path string, so camelCase or underscores inside a parameter name count against kebab-case. In our example below, renaming {id} to {userId} on two operations moved the score from 100 to 95.5. Treat that deduction as a prompt to check the name, not as a rule that {userId} is wrong.

JSON field names: one casing, chosen once

camelCase and snake_case both work. camelCase matches JavaScript and TypeScript property names, so frontend code can destructure a response without a mapping layer. snake_case matches Python and Ruby conventions and many database column names. Pick the one your main consumers write, set your serializer to emit it everywhere, and add a test that fails if a response contains the other style. An API that returns customer_id from one endpoint and customerId from another costs every client a lookup per field.

AvoidFour casings and no units

JSON
{
  "ID": "ord_81",
  "customer_id": "usr_12",
  "OrderStatus": "InProgress",
  "active": true,
  "created": 1767225600,
  "timeout": 30,
  "total": 1999
}

ID, customer_id and OrderStatus use three casings. active could be a verb or a state. created is a Unix timestamp with no hint of the unit or timezone. timeout and total leave the client to guess seconds or milliseconds, dollars or cents.

PreferOne casing, self-describing names

JSON
{
  "id": "ord_81",
  "customerId": "usr_12",
  "status": "in_progress",
  "isActive": true,
  "createdAt": "2026-01-01T00:00:00Z",
  "timeoutSeconds": 30,
  "totalCents": 1999
}

Every key is camelCase. The boolean reads as a question, the timestamp is ISO 8601 with a UTC offset, units sit in the names, and the enum value uses snake_case like every other enum in the API.

  • Acronyms: treat them as words. userId, htmlUrl and apiKey are easier to convert between casings than userID or HTMLURL, and code generators handle them predictably.
  • Do not repeat the resource name. Inside an order object, write status, not orderStatus. Keep the prefix only for references to other resources, such as customerId.
  • Money: store an integer in the smallest unit with the unit in the name, such as totalCents, plus a currency field with an ISO 4217 code.
  • Arrays: plural names (lineItems, tags). Single objects: singular names (shippingAddress).
  • Enums: one casing for every enum value in the API. Changing in_progress to inProgress later is a breaking change for every client that compares strings.

Query parameters

Query parameters should look like the fields they filter. If the response has status and createdAt, the filters are status and createdAfter or createdBefore, not state and from_date. Reserve a few names and use them on every list endpoint: limit and cursor (or page) for pagination, sort with a minus prefix for descending order, and fields if you support sparse responses.

HTTP
GET /v1/orders?status=paid&createdAfter=2026-01-01T00:00:00Z&sort=-createdAt&limit=50&cursor=b3JkXzgx
GET /v1/orders?status=paid&status=refunded
GET /v1/products?ids=prd_1,prd_2,prd_3
Filters named after fields, a reserved sort and pagination pair, and two ways to send multiple values. Document which one your API accepts.

For multiple values, choose repeated parameters (status=paid&status=refunded) or a comma-separated list (ids=prd_1,prd_2) and use the same form everywhere. In OpenAPI 3, style: form with explode: true describes the repeated form and explode: false describes the comma form, so generated clients serialize them correctly.

HTTP header names

RFC 9110 defines header field names as case-insensitive, so Request-Id and request-id are the same header and a client must not depend on the case it receives. Write them in documentation as hyphen-separated words with initial capitals, and never use underscores, because some proxies drop headers that contain them (nginx does by default). RFC 6648 (BCP 178) asks protocol designers to stop prefixing new parameters with X-, because experimental names leak into permanent use and then cannot be renamed. For a new header, write Request-Id or Idempotency-Key. If clients already send X-Request-ID, keep accepting it; renaming a working header breaks them for no gain.

Worked example: scoring a messy spec

Here is a small spec with naming problems that show up often: a verb in a collection path, a singular resource name, a snake_case segment and an action segment.

YAMLbefore.yaml
openapi: 3.0.3
info:
  title: Shop API
  version: 1.0.0
paths:
  /v1/getUsers:
    get:
      summary: List users
      responses:
        "200":
          description: A page of users
        "401":
          description: Missing or invalid token
  /v1/user/{user_id}:
    get:
      summary: Get one user
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The user
        "404":
          description: User not found
  /v1/user_profiles/{id}:
    patch:
      summary: Update a profile
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The updated profile
        "404":
          description: Profile not found
  /v1/orders/create:
    post:
      summary: Create an order
      responses:
        "201":
          description: Order created
        "400":
          description: Invalid order

APIForge scored it 86.6 out of 100. Naming dropped to 8.8 of its 20 points and HTTP usage to 17.8 of 20. It reported camelCase in /v1/getUsers, the verb segment create in /v1/orders/create (flagged twice, once as naming and once as a method conflict), an underscore in user_profiles, and the underscore in the {user_id} parameter. It did not flag the singular /v1/user, because its singular-versus-plural check fires only when both /user and /users appear as segments, and getUsers is not a users segment. A rule-based checker catches what its rules describe, so a human still needs to read the paths.

YAMLafter.yaml
openapi: 3.0.3
info:
  title: Shop API
  version: 1.0.0
paths:
  /v1/users:
    get:
      summary: List users
      responses:
        "200":
          description: A page of users
        "401":
          description: Missing or invalid token
  /v1/users/{id}:
    get:
      summary: Get one user
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The user
        "404":
          description: User not found
  /v1/user-profiles/{id}:
    patch:
      summary: Update a profile
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The updated profile
        "404":
          description: Profile not found
  /v1/orders:
    post:
      summary: Create an order
      responses:
        "201":
          description: Order created
        "400":
          description: Invalid order

After renaming to /v1/users, /v1/users/{id}, /v1/user-profiles/{id} and POST /v1/orders, the score is 100 with no issues. The score does not look at JSON field names or query parameters, so check those by hand against the cheat sheet above.

Renaming without breaking clients

Every rename in a published API is a breaking change. Clients have the old path or field name compiled into their code. Roll renames out in three steps: add the new name next to the old one, mark the old one deprecated in the spec (OpenAPI has deprecated: true on operations, parameters and schemas) and tell consumers when it goes away, then remove it in the next major version. For fields, return both names during the overlap. The API versioning guide covers deprecation windows and how to announce them.

Renames are cheapest before the first release. Write the conventions down in your repository, link them from the spec's info.description, and check new paths in review before the first client depends on them.

Check your paths in APIForge

Paste a spec URL or file into the API quality checker. It scores the API from 0 to 100 across seven categories, with naming worth 20 points, and lists each path that uses camelCase, underscores or verb segments, along with mixed singular and plural resource names. The glossary entry on REST APIs covers the resource model these naming rules come from.

Frequently asked questions

Use kebab-case with lowercase letters, such as /v1/user-profiles. Paths are case-sensitive under RFC 3986, so lowercase avoids duplicate URLs, and hyphens keep path segments distinct from the casing you use in JSON.

Either works if the whole API uses one. camelCase suits JavaScript and TypeScript consumers; snake_case suits Python and Ruby. Mixing them inside one API is the mistake to avoid.

The HTTP method already states the action, so POST /v1/orders creates an order without a /create segment. For actions that are not create, read, update or delete, create a sub-resource noun such as POST /v1/orders/{id}/cancellation.

Not for new headers. RFC 6648 deprecates the X- prefix because experimental names end up permanent anyway. Keep accepting X- headers your clients already send.

Sources

  1. RFC 3986: URI Generic Syntax, Section 6.2.2.1 Case NormalizationIETF
  2. RFC 9110: HTTP Semantics, Section 5.1 Field NamesIETF
  3. RFC 6648: Deprecating the "X-" PrefixIETF
  4. OpenAPI Specification 3.1.0: Path Templating and Parameter ObjectOpenAPI Initiative