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

MethodEndpointDescription
GET/api/v1/itemsList register items (paginated)
GET/api/v1/items/{id}Get single item by identifier
GET/api/v1/items/{id}/versionsGet version history for item
GET/api/v1/items/{id}/versions/{version}Get specific version
POST/api/v1/itemsCreate new item (governed: creates proposal)
PATCH/api/v1/items/{id}Update item (governed: creates proposal)
POST/api/v1/items/{id}/invalidateInvalidate item
POST/api/v1/items/{id}/supersedeSupersede with new item

Concept Endpoints (Concept Registers)

MethodEndpointDescription
GET/api/v1/conceptsList concepts
GET/api/v1/concepts/{id}Get concept with current definition
GET/api/v1/concepts/{id}/hierarchyGet concept hierarchy (is-a, has-part)
GET/api/v1/concepts/{id}/itemsGet items linked to concept

Governance Endpoints (Governed Registers)

MethodEndpointDescription
GET/api/v1/proposalsList proposals (filtered by status)
GET/api/v1/proposals/{id}Get proposal details
POST/api/v1/proposalsSubmit new proposal
POST/api/v1/proposals/{id}/approveApprove proposal (Control Body)
POST/api/v1/proposals/{id}/rejectReject proposal (Control Body)
GET/api/v1/audit-logQuery 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 layer

Best for: Government, financial, high-value registers

Authorization Model

Map FERIN roles to API permissions:

RoleReadProposeApproveAdmin
AnonymousYes*NoNoNo
Authenticated UserYesYesNoNo
Register ManagerYesYesLimitedYes
Control BodyYesYesYesNo
Register OwnerYesYesYesYes

* 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=20

Filtering

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=-added

Rate 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: 3600

Recommended Limits

OperationAnonymousAuthenticatedTrusted
Read operations100/hour1000/hour10000/hour
Write operationsN/A50/hour500/hour
Search queries30/hour300/hour3000/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'

Related Topics