API Design Standards

evergreenLast update on Jul 9, 2026
Download .md

URL Structure

Gunakan plural nouns untuk resource:

GET    /api/v1/users          # List users
POST   /api/v1/users          # Create user
GET    /api/v1/users/:id      # Get user by ID
PUT    /api/v1/users/:id      # Update user
DELETE /api/v1/users/:id      # Delete user
GET    /api/v1/users/:id/posts # Nested resource

Versioning

Gunakan prefix /v1/, /v2/ di URL. Hanya increment kalau ada breaking changes.

Response Format

Success

Semua response sukses mengikuti envelope yang konsisten:

{
  "data": { ... } | [ ... ],
  "meta": { ... }    // optional, hanya untuk collection/list
}

Single Object (data adalah objek)

Untuk endpoint yang return satu resource:

{
  "data": {
    "id": "usr_2v6d8x1",
    "name": "Ahmad Musafir",
    "email": "khoirul@example.com",
    "role": "admin",
    "created_at": "2026-07-01T08:00:00Z"
  }
}

Contoh endpoint:

GET    /api/v1/users/:id      → { data: User }
POST   /api/v1/users          → { data: User }              // 201
PUT    /api/v1/users/:id      → { data: User }
DELETE /api/v1/users/:id      → { data: null }              // 204, body optional

Collection / Array (data adalah array)

Untuk endpoint yang return list:

{
  "data": [
    {
      "id": "usr_2v6d8x1",
      "name": "Ahmad Musafir",
      "email": "khoirul@example.com"
    },
    {
      "id": "usr_9k3m5p2",
      "name": "John Doe",
      "email": "john@example.com"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 150,
    "total_pages": 8
  }
}

Contoh endpoint:

GET /api/v1/users             → { data: User[], meta: PaginationMeta }
GET /api/v1/users/:id/posts   → { data: Post[], meta: PaginationMeta }
GET /api/v1/search            → { data: Result[], meta: PaginationMeta }

Aturan data

KondisiBentuk datameta
Single resource (GET by ID)ObjectTidak ada
Create / Update / DeleteObjectTidak ada
List / Search / FilterArrayWajib ada
Empty listArray kosong []Wajib ada
Endpoint non-resource (health)Object atau primitiveTidak ada

WARNING

Jangan kirim object langsung tanpa data wrapper. Jangan kirim array langsung di root tanpa envelope. Konsistensi envelope memudahkan client handling global.

Error

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      }
    ]
  }
}

HTTP Status Codes

CodeDescriptionWhen
200OKGET, PUT sukses
201CreatedPOST sukses
204No ContentDELETE sukses
400Bad RequestValidasi gagal
401UnauthorizedBelum login
403ForbiddenTidak punya akses
404Not FoundResource tidak ditemukan
409ConflictDuplicate data
422UnprocessableValidasi business logic
429Too Many RequestsRate limit
500Internal ErrorServer error (jangan expose detail)

Pagination

Gunakan cursor-based untuk list besar, page-based untuk list kecil:

{
  "data": [...],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 150,
    "total_pages": 8
  }
}

Query params: ?page=1&per_page=20 atau ?cursor=eyJpZCI6MTB9&limit=20.

Authentication

Gunakan Bearer token di header:

Authorization: Bearer <token>

Rate Limiting

Return header:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1620000000