Skip to content

Resource Objects

Standard structure for representing domain entities


Resource objects represent domain entities (database records, API resources, etc.) with a consistent structure. This enables predictable data access, relationship handling, and query operations.


A resource object MUST contain:

{
"type": "order",
"id": "12345",
"attributes": {
"status": "pending",
"total": { "amount": "99.99", "currency": "USD" },
"created_at": "2024-01-15T10:30:00Z"
}
}
MemberTypeDescription
typestringResource type identifier
idstringUnique identifier within type
MemberTypeDescription
attributesobjectResource attributes
relationshipsobjectRelated resources
metaobjectNon-standard meta-information

The type member identifies the resource’s type.

  • MUST be a string
  • MUST contain at least one character
  • MUST be unique across the API
  • SHOULD use snake_case for multi-word types
  • SHOULD be singular (e.g., order not orders)
  • SHOULD match the domain model name
order
order_item
shipping_address
tracking_event
customer
orders # Plural - use singular
orderItem # camelCase - use snake_case
Order # PascalCase - use lowercase
order-item # kebab-case - use snake_case

The id member uniquely identifies a resource within its type.

  • MUST be a string
  • MUST be unique within the resource type
  • MUST NOT change for the lifetime of the resource
  • SHOULD be URL-safe

Recommended formats:

FormatExampleNotes
UUID v4"550e8400-e29b-41d4-a716-446655440000"Universal uniqueness
ULID"01ARZ3NDEKTSV4RRFFQ69G5FAV"Sortable, compact
Prefixed"order_abc123xyz"Type-aware, readable
Integer (as string)"12345"Database primary keys

IDs MUST be strings even for numeric values:

// Correct
{ "type": "order", "id": "12345" }
// Incorrect
{ "type": "order", "id": 12345 }

The attributes member contains the resource’s data.

  • MUST be an object
  • MUST NOT contain id or type (these are top-level)
  • MUST NOT contain relationship data
  • MAY contain any valid JSON value

Attribute names SHOULD:

  • Use snake_case
  • Be descriptive
  • Avoid abbreviations
{
"type": "order",
"id": "12345",
"attributes": {
"order_number": "ORD-2024-001",
"status": "pending",
"total_amount": { "amount": "99.99", "currency": "USD" },
"item_count": 3,
"is_gift": false,
"notes": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z"
}
}

Attributes MAY contain nested objects and arrays:

{
"type": "customer",
"id": "42",
"attributes": {
"name": "Alice Smith",
"email": "alice@example.com",
"address": {
"line1": "123 Main St",
"line2": null,
"city": "Helsinki",
"postal_code": "00100",
"country_code": "FI"
},
"tags": ["vip", "wholesale"],
"preferences": {
"newsletter": true,
"language": "en"
}
}
}
JSON TypeUsage
stringText, enums, dates (ISO 8601), UUIDs
numberIntegers, floats (avoid for currency)
booleanTrue/false flags
nullAbsent or unknown values
objectStructured data, value objects
arrayLists, collections

Money SHOULD use a structured format to avoid floating-point issues:

{
"total": {
"amount": "99.99",
"currency": "USD"
}
}

Dates and times MUST use ISO 8601 format:

{
"created_at": "2024-01-15T10:30:00Z",
"scheduled_date": "2024-01-20",
"duration": { "value": 30, "unit": "minute" }
}

The relationships member describes connections to other resources.

{
"type": "order",
"id": "12345",
"attributes": { ... },
"relationships": {
"customer": {
"data": { "type": "customer", "id": "42" }
},
"items": {
"data": [
{ "type": "order_item", "id": "1" },
{ "type": "order_item", "id": "2" }
]
},
"shipping_address": {
"data": null
}
}
}

Each relationship MUST contain a data member:

data ValueMeaning
ObjectTo-one relationship (resource identifier)
ArrayTo-many relationship (resource identifiers)
nullEmpty to-one relationship
[]Empty to-many relationship

A resource identifier contains only type and id:

{ "type": "customer", "id": "42" }

This is NOT a full resource object—it’s a reference.

Relationship names SHOULD:

  • Use snake_case
  • Be descriptive of the relationship
  • Match the related resource type when appropriate
{
"relationships": {
"customer": { ... }, // Matches type
"billing_address": { ... }, // Descriptive
"shipping_address": { ... }, // Descriptive
"created_by": { ... }, // Describes relationship
"items": { ... } // Plural for to-many
}
}

When including related resources, use the included array:

{
"data": {
"type": "order",
"id": "12345",
"attributes": {
"status": "pending"
},
"relationships": {
"customer": {
"data": { "type": "customer", "id": "42" }
}
}
},
"included": [
{
"type": "customer",
"id": "42",
"attributes": {
"name": "Alice",
"email": "alice@example.com"
}
}
]
}
  • included MUST be an array of resource objects
  • Each included resource MUST be unique (by type + id)
  • Included resources MUST be referenced by at least one relationship
  • Included resources MAY have their own relationships

See Relationships for inclusion details.


The meta member contains non-standard information:

{
"type": "order",
"id": "12345",
"attributes": { ... },
"meta": {
"permissions": ["read", "update"],
"cache_expires_at": "2024-01-15T11:00:00Z",
"version": 3
}
}
  • Permission indicators
  • Cache control hints
  • Version numbers
  • Computed values not in attributes

Services SHOULD define their resources with allowed fields, filters, relationships, and sorts:

// Conceptual resource definition
{
"type": "order",
"fields": {
"self": ["id", "order_number", "status", "total_amount", "created_at", "updated_at"],
"customer": ["id", "name", "email"],
"items": ["id", "sku", "quantity", "price"]
},
"filters": {
"self": ["id", "order_number", "status", "created_at"],
"customer": ["id", "name"]
},
"sorts": {
"self": ["order_number", "status", "created_at", "total_amount"]
},
"relationships": ["customer", "items", "shipping_address", "billing_address"]
}

This definition:

  • Whitelists queryable fields
  • Whitelists filterable attributes
  • Whitelists sortable attributes
  • Defines available relationships

Clients MAY request specific fields:

{
"fields": {
"self": ["id", "status", "total_amount"],
"customer": ["id", "name"]
}
}

Response includes only requested fields:

{
"type": "order",
"id": "12345",
"attributes": {
"status": "pending",
"total_amount": { "amount": "99.99", "currency": "USD" }
}
}

See Sparse Fieldsets for details.


{
"type": "customer",
"id": "42",
"attributes": {
"name": "Alice Smith",
"email": "alice@example.com",
"created_at": "2024-01-10T08:00:00Z"
}
}
{
"type": "order",
"id": "12345",
"attributes": {
"order_number": "ORD-2024-001",
"status": "shipped",
"total_amount": { "amount": "249.99", "currency": "USD" },
"created_at": "2024-01-15T10:30:00Z"
},
"relationships": {
"customer": {
"data": { "type": "customer", "id": "42" }
},
"items": {
"data": [
{ "type": "order_item", "id": "101" },
{ "type": "order_item", "id": "102" }
]
},
"shipping_address": {
"data": { "type": "address", "id": "addr_789" }
}
}
}
{
"type": "document",
"id": "doc_abc",
"attributes": {
"title": "Q4 Report",
"content_type": "application/pdf",
"size_bytes": 1048576
},
"meta": {
"download_url": "https://...",
"expires_at": "2024-01-16T10:30:00Z",
"permissions": ["read", "download"]
}
}
{
"data": [
{
"type": "tracking_event",
"id": "evt_001",
"attributes": {
"status": "in_transit",
"location": "Helsinki Hub",
"occurred_at": "2024-01-15T14:30:00Z"
}
},
{
"type": "tracking_event",
"id": "evt_002",
"attributes": {
"status": "out_for_delivery",
"location": "Local Depot",
"occurred_at": "2024-01-15T16:00:00Z"
}
}
],
"meta": {
"page": {
"cursor": {
"current": "...",
"next": null
}
}
}
}
{
"data": {
"type": "shipment",
"id": "ship_12345",
"attributes": {
"tracking_number": "MH726955185FI",
"status": "in_transit",
"carrier": "posti",
"created_at": "2024-01-14T09:00:00Z"
},
"relationships": {
"origin": {
"data": { "type": "location", "id": "loc_001" }
},
"destination": {
"data": { "type": "location", "id": "loc_002" }
},
"events": {
"data": [
{ "type": "tracking_event", "id": "evt_001" },
{ "type": "tracking_event", "id": "evt_002" }
]
}
}
},
"included": [
{
"type": "location",
"id": "loc_001",
"attributes": {
"name": "Helsinki Warehouse",
"postal_code": "00100",
"country_code": "FI"
}
},
{
"type": "location",
"id": "loc_002",
"attributes": {
"name": "Tampere Office",
"postal_code": "33100",
"country_code": "FI"
}
},
{
"type": "tracking_event",
"id": "evt_001",
"attributes": {
"status": "picked_up",
"occurred_at": "2024-01-14T10:00:00Z"
}
},
{
"type": "tracking_event",
"id": "evt_002",
"attributes": {
"status": "in_transit",
"occurred_at": "2024-01-15T08:00:00Z"
}
}
]
}