API Design Patterns
Designing REST and GraphQL APIs for FERIN registers. Patterns, templates, and best practices for integrators.
API Design Principles
A well-designed register API enables discovery, integration, and automation. Follow these principles:
Resource-Oriented
Model APIs around register resources: items, concepts, proposals, not actions.
Versioned
Include API versions in URLs or headers to enable evolution without breaking clients.
Discoverable
Provide links, documentation, and metadata so clients can explore without prior knowledge.
Consistent
Use consistent naming, pagination, error formats, and conventions across all endpoints.
REST API Patterns
REST is the most common pattern for register APIs. Here are recommended endpoint structures.
Resource Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/items | List register items (paginated) |
| GET | /api/v1/items/{id} | Get single item by identifier |
| GET | /api/v1/items/{id}/versions | Get version history for item |
| GET | /api/v1/items/{id}/versions/{version} | Get specific version |
| POST | /api/v1/items | Create new item (governed: creates proposal) |
| PATCH | /api/v1/items/{id} | Update item (governed: creates proposal) |
| POST | /api/v1/items/{id}/invalidate | Invalidate item |
| POST | /api/v1/items/{id}/supersede | Supersede with new item |
Concept Endpoints (Concept Registers)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/concepts | List concepts |
| GET | /api/v1/concepts/{id} | Get concept with current definition |
| GET | /api/v1/concepts/{id}/hierarchy | Get concept hierarchy (is-a, has-part) |
| GET | /api/v1/concepts/{id}/items | Get items linked to concept |
Governance Endpoints (Governed Registers)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/proposals | List proposals (filtered by status) |
| GET | /api/v1/proposals/{id} | Get proposal details |
| POST | /api/v1/proposals | Submit new proposal |
| POST | /api/v1/proposals/{id}/approve | Approve proposal (Control Body) |
| POST | /api/v1/proposals/{id}/reject | Reject proposal (Control Body) |
| GET | /api/v1/audit-log | Query audit log |
Response Formats
Item Response
{
"id": "urn:iso:std:iso:19135:rum:a1b2c3d4",
"functionalId": "rum:m",
"type": "RegisterItem",
"content": {
"symbol": "m",
"name": "meter",
"definition": "The metre is the length of the path...",
"unitType": "base"
},
"status": {
"valid": true,
"published": true,
"supersededBy": null
},
"concept": {
"id": "urn:iso:std:iso:19135:rum:concept:meter",
"href": "/api/v1/concepts/meter"
},
"dates": {
"added": "1960-10-14",
"modified": "1983-10-20",
"invalidated": null
},
"version": {
"number": "2.0.0",
"href": "/api/v1/items/rum:m/versions/2.0.0"
},
"_links": {
"self": { "href": "/api/v1/items/rum:m" },
"versions": { "href": "/api/v1/items/rum:m/versions" },
"concept": { "href": "/api/v1/concepts/meter" },
"register": { "href": "/api/v1" }
}
}Paginated List Response
{
"items": [ ... ],
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 156,
"totalPages": 8
},
"_links": {
"self": { "href": "/api/v1/items?page=1" },
"next": { "href": "/api/v1/items?page=2" },
"last": { "href": "/api/v1/items?page=8" }
}
}Error Response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid item content",
"details": [
{
"field": "content.symbol",
"message": "Symbol must be unique",
"value": "m"
}
],
"requestId": "req_abc123"
}
}Authentication & Authorization
Authentication Patterns
API Keys
Simple, stateless authentication for server-to-server access.
Authorization: ApiKey sk_live_abc123...Best for: Read-only access, trusted integrations
OAuth 2.0 / OIDC
Delegated authorization with user context.
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...Best for: User actions, governed operations
mTLS
Mutual TLS for high-security environments.
Client certificate verified at TLS layerBest for: Government, financial, high-value registers
Authorization Model
Map FERIN roles to API permissions:
| Role | Read | Propose | Approve | Admin |
|---|---|---|---|---|
| Anonymous | Yes* | No | No | No |
| Authenticated User | Yes | Yes | No | No |
| Register Manager | Yes | Yes | Limited | Yes |
| Control Body | Yes | Yes | Yes | No |
| Register Owner | Yes | Yes | Yes | Yes |
* Subject to access commitments and publication status
Pagination and Filtering
Pagination
Use cursor-based pagination for large datasets:
# Cursor-based (recommended)
GET /api/v1/items?cursor=eyJpZCI6MTAwfQ&limit=20
# Offset-based (acceptable for small sets)
GET /api/v1/items?page=2&pageSize=20Filtering
Support common filter patterns:
# Status filters
GET /api/v1/items?status=valid,published
# Date range filters
GET /api/v1/items?addedAfter=2024-01-01&addedBefore=2024-12-31
# Content filters (register-specific)
GET /api/v1/items?unitType=base
# Full-text search
GET /api/v1/items?q=meter
# Combined filters
GET /api/v1/items?status=valid&unitType=base&sort=-addedRate Limiting
Protect your API with rate limiting. Communicate limits via headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200
Retry-After: 3600Recommended Limits
| Operation | Anonymous | Authenticated | Trusted |
|---|---|---|---|
| Read operations | 100/hour | 1000/hour | 10000/hour |
| Write operations | N/A | 50/hour | 500/hour |
| Search queries | 30/hour | 300/hour | 3000/hour |
GraphQL Alternative
For complex queries, GraphQL can provide more flexibility than REST.
Schema Structure
type Query {
item(id: ID!): RegisterItem
items(filter: ItemFilter, page: Pagination): ItemConnection!
concept(id: ID!): Concept
concepts(filter: ConceptFilter): ConceptConnection!
proposal(id: ID!): Proposal
proposals(filter: ProposalFilter): ProposalConnection!
}
type RegisterItem {
id: ID!
functionalId: String
content: ItemContent!
status: ItemStatus!
concept: Concept
versions: [ItemVersion!]!
dates: ItemDates!
}
type Concept {
id: ID!
definition: String!
parents: [Concept!]!
children: [Concept!]!
items: [RegisterItem!]!
}Query Example
query GetUnitWithHierarchy {
item(id: "rum:m") {
functionalId
content {
symbol
name
definition
}
concept {
definition
parents {
definition
}
}
versions(first: 5) {
edges {
node {
version
date
}
}
}
}
}OpenAPI Specification Template
Use this template to document your register API:
openapi: 3.1.0
info:
title: FERIN Register API
version: 1.0.0
description: API for [Register Name]
servers:
- url: https://api.example.org/v1
paths:
/items:
get:
summary: List register items
parameters:
- name: status
in: query
schema:
type: array
items:
type: string
enum: [valid, invalid, published, unpublished]
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: Paginated list of items
content:
application/json:
schema:
$ref: '#/components/schemas/ItemListResponse'
components:
schemas:
RegisterItem:
type: object
required: [id, content, status]
properties:
id:
type: string
format: uri
functionalId:
type: string
content:
type: object
status:
$ref: '#/components/schemas/ItemStatus'