A well-designed API feels obvious. Endpoints are predictable, responses are consistent, errors are helpful. A poorly designed API creates confusion, support tickets, and workarounds.

These conventions create APIs that are intuitive to integrate.

Resource Naming

Use nouns, not verbs. Plural for collections:

βœ…GGPPD❌GPGPEEOUEEOEOGTTSTLBTSTSoTEaTToTddE:://///////uuuuugcuusssssersseeeeeteeerrrrrUarrsssssst/s///ee1/111rU21222ss32333e3r/delet#####eLGCUDierpestedltaaeutttuseeeseeruuurssss1eee2rrr3112233

Nested resources for relationships:

GGPEEOTTST///upusosesertrsss///141252363///pcpooosmstmtsesnts###PCCoorsmetmasetnebtyspouossnterpfoo1sr2t3u4s5e6r123

Consistent Response Structure

Every response follows the same pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Success
{
  "data": {
    "id": "123",
    "type": "user",
    "attributes": {
      "name": "Alice",
      "email": "alice@example.com"
    }
  }
}

// Collection
{
  "data": [
    {"id": "123", "type": "user", "attributes": {...}},
    {"id": "124", "type": "user", "attributes": {...}}
  ],
  "meta": {
    "total": 100,
    "page": 1,
    "per_page": 20
  }
}

// Error
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "details": [
      {"field": "email", "message": "cannot be blank"}
    ]
  }
}

Clients should always know where to find data, metadata, and errors.

HTTP Status Codes

Use them correctly:

222444444555000000002000014013492023OCNBUFNCUIBSKroanooonnaeedartnptdraCubfrevtoRtiFlorGienehdoicnacdtqoduceateeurentsleneindswUtszaEantebryadlrveoa--------ri-lSRSIAARS--aueunuuetVbcscvttsaaSUlcocahhotlepeeueleeueirssrsinnrdvt-scsdttccaere,iieotreT(iccniaeGcnnaadfobmmEropttolnupTeuieeigso,abtodsceertonntrraPedb'rvrUdyrut(oiiTetdrcl,((qeuseyPDunxpPOEioilfdASLrtsiaoTTEetciwC)TdaalnHElte))ledo,weedtc.)

Don’t return 200 with {"success": false}. Use proper status codes.

Pagination

Always paginate collections:

GET/users?page=2&per_page=20

Response includes pagination metadata:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "data": [...],
  "meta": {
    "pagination": {
      "total": 253,
      "page": 2,
      "per_page": 20,
      "total_pages": 13
    }
  },
  "links": {
    "self": "/users?page=2&per_page=20",
    "first": "/users?page=1&per_page=20",
    "prev": "/users?page=1&per_page=20",
    "next": "/users?page=3&per_page=20",
    "last": "/users?page=13&per_page=20"
  }
}

For large datasets, use cursor-based pagination:

GET/events?cursor=eyJpZCI6MTIzfQ&limit=50

Filtering and Sorting

Consistent query parameter patterns:

#GGG#GGG#G#GEEEEEEEEFTTTSTTTFTITioinl///r///e/c/tuuutuuululuesssisssdsusreeeneeeedeirrrgrrrsrirnssssssesnsg??????l?g?srcsssefitorooocirnalerrrteecteatttilllu=t===odausaec-nnstd=ddrca=eeam_ermid=ciaaeedptnfta,,roi&tet-nesvsedecastetr_drmosa=a_eeu,t2taa,rpu0ttecrs2emeo=4dasfa-_iic0allt1tei-v0###e1ADMseucslectneidnpidlniegngfields

Error Responses

Errors should be helpful:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address"
      },
      {
        "field": "age",
        "code": "OUT_OF_RANGE",
        "message": "Must be between 18 and 120"
      }
    ],
    "request_id": "req_abc123"
  }
}

Include:

  • Machine-readable error code
  • Human-readable message
  • Field-level details for validation errors
  • Request ID for debugging

Versioning

Version your API from day one:

#GG#GAEEEcUTTHTcReeLa/pvdutp12es:a//retuurahssvspeeepvrrrlesssiricsoaintoiinnoignn/gvn(dr.emcyoampmie.nvd2e+dj)son

Authentication

Use standard mechanisms:

#GX#GAE-EuATABTtPPehI/Ia/ou-rurKsKesieeerezyryras:Tst(oisskoikenm_n:plli(BevOe)eAa_uratebhrc21/e2Jy3WJTh)bGciOiJIUzI1NiIs...

Return 401 with clear error:

1
2
3
4
5
6
{
  "error": {
    "code": "INVALID_TOKEN",
    "message": "The access token has expired"
  }
}

Rate Limiting

Return rate limit info in headers:

HXXXT---TRRRPaaa/ttt1eee.LLL1iiimmm2iii0ttt0---LRROieeKmmsiaetit:n:i1n10g60:400909070000

When exceeded:

HR{}TeTt"}Pre/yr""1-rcm.Aooe1frdst"es4e:"a2r:g9:{e""T6R:o0AoT"ERM_aaLtnIeyMIlRTie_mqEiuXteCsEetExsDcEeDe"d,ed.Retryafter60seconds."

Timestamps

Use ISO 8601 with timezone:

1
2
3
4
{
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T14:22:15Z"
}

Always use UTC. Let clients convert to local time.

Null vs Absent

Be consistent about null values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Option 1: Include null fields
{
  "name": "Alice",
  "nickname": null,
  "avatar_url": null
}

// Option 2: Omit null fields
{
  "name": "Alice"
}

Pick one approach and stick to it. Document your choice.

Bulk Operations

For batch operations, use a consistent pattern:

PC{}OoSn"]Ttoep{{{/ne"""utrmmms-aeeeeTttttryihhhspoooo/endddb:s"""u":::la:kp"""p[cudlrpeiedlcaaeatttteeei"""o,,,n/"""jdiisaddot""na::":""14{25"36n""a,}me""d:at"aA"l:ic{e""n}a}m,e":"Bob"}},

Response indicates per-operation results:

1
2
3
4
5
6
7
{
  "results": [
    {"status": "success", "data": {"id": "789"}},
    {"status": "success", "data": {"id": "123"}},
    {"status": "error", "error": {"code": "NOT_FOUND"}}
  ]
}

HATEOAS (When It Helps)

Include links to related actions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "data": {
    "id": "123",
    "status": "pending",
    "links": {
      "self": "/orders/123",
      "approve": "/orders/123/approve",
      "cancel": "/orders/123/cancel",
      "items": "/orders/123/items"
    }
  }
}

Don’t over-engineer it. Add links that genuinely help clients navigate.

Documentation

Document every endpoint:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
## Create User

`POST /users`

Creates a new user account.

### Request

```json
{
  "name": "Alice",
  "email": "alice@example.com",
  "role": "member"
}

Response

201 Created

1
2
3
4
5
6
7
{
  "data": {
    "id": "123",
    "name": "Alice",
    "email": "alice@example.com"
  }
}

Errors

CodeDescription
400Invalid request body
409Email already exists
GPoiocdkAcPoInvdeenstiigonnsiseaarbloyu.tDcoocnusmiesnttentchyema.ndEnpfroerdciecttahbeimliitny.coWdheenredveiveewl.opTehresAcPaIntghuaets'ssheoawsyantoenidnptoeignrtatweoriksstbheefoArPeIrtehaadtinggettsheaddoopctse,d.you'vedoneitright.