Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

HATEOAS+Context Specification v1.0

Business Context for Hypermedia APIs

Abstract

This specification extends HATEOAS (Hypermedia as the Engine of Application State) with business context metadata, enabling APIs to share meaningful business information that has presentation implications. This allows clients to make intelligent UI decisions without duplicating business logic, while maintaining proper separation of concerns between backend business rules and frontend presentation logic.

1. Introduction

1.1 The Problem

If you've built a HATEOAS API, you know the dirty secret: clients hardcode URLs anyway. They ignore your carefully crafted links. Meanwhile, frontend developers duplicate your business logic just to figure out whether to show a button as disabled or hide it entirely.

This specification tackles both problems head-on. We enrich hypermedia responses with business context that actually helps frontends make intelligent decisions. The backend stays in charge of business rules, the frontend stays in charge of pixels and animations, and everyone's happy.

1.2 Core Philosophy: Business Context, Not UI Instructions

This specification has strong opinions about what belongs where. The backend should never tell the frontend to "show a red button" or "use a modal dialog." That's none of the backend's business. What the backend SHOULD communicate is "this action will permanently delete data" or "this needs to happen in the next 3 hours."

The frontend owns everything visual: colors, animations, whether something slides in from the left or fades in from above. The backend owns the business reality: this customer is high-value, this order expires tomorrow, this action requires manager approval.

When you understand this distinction, everything else falls into place.

1.3 Design Principles

We built this spec around four non-negotiable principles. First, everything is business-first. If you catch yourself thinking about buttons or colors, you're doing it wrong. Second, context is always optional. Your API works without it, but it works better with it. Third, we describe meaning, never appearance. And fourth, this works everywhere: web, mobile, voice assistants, CLI tools, whatever comes next.

2. Architectural Boundaries

2.1 Backend Responsibilities (Business Context)

The backend provides:

{
  "subscription": {
    "status": "trial",
    "expiresIn": "3 days",
    "_links": {
      "upgrade": {
        "href": "/subscription/upgrade",
        "_context": {
          "urgency": "time_sensitive",
          "reason": "trial_ending_soon",
          "businessImpact": "service_interruption"
        }
      }
    }
  }
}

2.2 Frontend Responsibilities (UI Decisions)

The frontend decides:

  • Show urgency as red button, banner, or modal?
  • What animation to use?
  • Where to position the element?
  • What icon or imagery to include?

2.3 The Contract

The backend provides facts: "Trial expires in 3 days, upgrade available." The frontend interprets those facts through its design system. Maybe that's a red banner on web, a push notification on mobile, or an urgent voice prompt on Alexa. The backend doesn't know or care.

3. Core Concepts

3.1 Context Metadata Object

The _context object is where the magic happens. It can appear at three levels: on resources (what kind of data is this?), on links (what does this action mean?), and on fields (what rules apply here?).

3.2 Structure

{
  "data": { /* resource data */ },
  "_links": {
    "self": { "href": "/resource/123" },
    "action": {
      "href": "/resource/123/action",
      "method": "POST",
      "_context": { /* action business context */ }
    }
  },
  "_context": { /* resource business context */ }
}

4. Resource-Level Business Context

4.1 Data Structure Hints

Your API knows what kind of data it's serving. Share that knowledge:

{
  "users": [...],
  "_context": {
    "dataStructure": "collection",
    "businessPurpose": "operational",
    "capabilities": ["sortable", "filterable", "exportable"],
    "businessProperties": {
      "identityField": "email",
      "operationalSummary": ["name", "role", "lastActive"],
      "auditDetails": ["createdAt", "permissions", "department"]
    },
    "businessOrdering": {
      "defaultSort": "lastActive",
      "businessRationale": "operational_efficiency_prioritizes_active_users"
    }
  }
}

4.2 Business Relationships

{
  "invoice": {
    "id": "INV-2024-001",
    "amount": 1500.00,
    "status": "overdue",
    "_context": {
      "businessState": "requires_immediate_attention",
      "relatedConcepts": {
        "customer": {
          "importance": "high",
          "reason": "key_account"
        },
        "payment_history": {
          "importance": "relevant",
          "reason": "multiple_late_payments"
        }
      },
      "possibleOutcomes": [
        "payment_received",
        "send_to_collections",
        "negotiate_payment_plan"
      ]
    }
  }
}

5. Link-Level Business Context

5.1 Action Context

{
  "_links": {
    "cancel_subscription": {
      "href": "/subscription/cancel",
      "method": "POST",
      "_context": {
        "businessImpact": "permanent_data_loss",
        "requiresConfirmation": true,
        "confirmationContext": {
          "dataAtRisk": "2.5GB of stored data",
          "alternativeActions": ["downgrade", "pause"],
          "coolingOffPeriod": "30 days"
        },
        "availability": {
          "reason": "active_subscription",
          "restrictions": ["cannot_cancel_during_billing_cycle"]
        }
      }
    }
  }
}

5.2 Business Process Context

{
  "_links": {
    "submit_order": {
      "href": "/orders",
      "method": "POST",
      "_context": {
        "processType": "user_input_required",
        "businessRules": {
          "requiredInformation": [
            {
              "field": "shipping_address",
              "reason": "physical_delivery",
              "businessRule": "required_for_physical_products"
            },
            {
              "field": "tax_id",
              "reason": "business_purchase",
              "condition": "when_amount_exceeds_1000"
            }
          ],
          "validationContext": {
            "inventory": "real_time_check",
            "pricing": "promotional_rules_apply"
          }
        }
      }
    }
  }
}

6. Field-Level Business Context

6.1 Business Rules and Constraints

{
  "fields": [
    {
      "name": "credit_limit",
      "_context": {
        "businessType": "currency",
        "constraints": {
          "minimum": 0,
          "maximum": 50000,
          "reason": "regulatory_compliance",
          "increment": 100
        },
        "businessLogic": {
          "automaticAdjustment": true,
          "adjustmentFactors": ["payment_history", "credit_score"],
          "requiresApproval": "when_exceeds_10000"
        }
      }
    },
    {
      "name": "tax_jurisdiction",
      "_context": {
        "businessType": "regulatory_selection",
        "selectionContext": {
          "dependsOn": ["business_address", "customer_type"],
          "dataSource": "tax_authority_api",
          "updateFrequency": "quarterly"
        }
      }
    }
  ]
}

6.2 Conditional Business Logic

{
  "fields": [
    {
      "name": "international_shipping",
      "_context": {
        "availability": {
          "condition": "product_type_allows_export",
          "additionalRequirements": ["customs_declaration", "export_license"]
        },
        "businessImpact": {
          "cost": "additional_fees_apply",
          "timeline": "extended_delivery",
          "compliance": "export_regulations"
        }
      }
    }
  ]
}

7. Business Context Vocabulary

7.1 Action Context Types

  • businessImpact: The business consequence of an action

    • reversible - Can be undone
    • permanent_data_loss - Unrecoverable deletion
    • financial_commitment - Involves money
    • service_interruption - Affects availability
    • compliance_violation - Regulatory impact
  • urgency: Business-driven time sensitivity

    • immediate - Requires action now
    • time_sensitive - Has a deadline
    • convenience - At user's discretion
  • importance: Business priority

    • critical - Core business function
    • primary - Main user journey
    • secondary - Supporting function
    • optional - Nice to have

7.2 Data Context Types

  • dataStructure: The nature of the data

    • single_entity - One object
    • collection - Multiple related items
    • hierarchical - Tree structure
    • grouped - Categorized data
    • time_series - Temporal data
  • businessPurpose: Why this data exists

    • transactional - Records business transactions
    • reference - Lookup/configuration data
    • analytical - Reporting/insights
    • operational - Day-to-day operations

8. Examples: Business Context in Practice

8.1 Order Management System

{
  "order": {
    "id": "ORD-2024-1234",
    "status": "pending_payment",
    "total": 2499.99,
    "items": [...],
    "_links": {
      "cancel": {
        "href": "/orders/ORD-2024-1234/cancel",
        "method": "POST",
        "_context": {
          "availability": {
            "available": true,
            "reason": "payment_not_processed",
            "expires": "2024-01-20T15:00:00Z"
          },
          "businessImpact": "reversible",
          "alternativeActions": ["modify", "hold"]
        }
      },
      "pay": {
        "href": "/orders/ORD-2024-1234/payment",
        "method": "POST",
        "_context": {
          "urgency": "time_sensitive",
          "deadline": "2024-01-20T15:00:00Z",
          "requiredInformation": [
            {
              "type": "payment_method",
              "acceptedMethods": ["credit_card", "bank_transfer"],
              "restrictions": {
                "minimum_amount": 100,
                "currency": "USD"
              }
            }
          ],
          "promotions": {
            "available": true,
            "description": "10% off for immediate payment"
          }
        }
      }
    },
    "_context": {
      "businessState": "requires_customer_action",
      "stateTransitions": {
        "next_states": ["paid", "cancelled", "expired"],
        "automatic_transition": {
          "to": "expired",
          "condition": "payment_deadline_passed"
        }
      },
      "customerContext": {
        "is_returning": true,
        "lifetime_value": "high",
        "payment_reliability": "excellent"
      }
    }
  }
}

8.2 User Access Control

{
  "user": {
    "id": 456,
    "name": "Jane Smith",
    "role": "analyst",
    "_links": {
      "grant_admin": {
        "href": "/users/456/roles",
        "method": "PUT",
        "_context": {
          "available": false,
          "reason": "insufficient_privileges",
          "requirements": {
            "requester_role": "super_admin",
            "approval_needed": true,
            "business_justification": "required"
          },
          "businessImpact": "security_elevation",
          "auditTrail": "required_by_compliance"
        }
      },
      "reset_password": {
        "href": "/users/456/password-reset",
        "method": "POST",
        "_context": {
          "triggerType": "administrative_action",
          "userNotification": "required",
          "securityImplications": {
            "forces_relogin": true,
            "invalidates_sessions": "all_devices",
            "notification_channels": ["email", "sms"]
          }
        }
      }
    },
    "_context": {
      "accountStatus": {
        "security": "good_standing",
        "lastSecurityReview": "2024-01-01",
        "riskFactors": [],
        "complianceFlags": ["gdpr_consent_current", "terms_accepted"]
      },
      "organizationalContext": {
        "department": "finance",
        "dataAccess": "sensitive_financial",
        "managementLevel": "individual_contributor"
      }
    }
  }
}

9. Implementation Guidelines

9.1 Backend Best Practices

When implementing this, ask yourself what the business knows, not how things should look. If you're tempted to include "widget" or "red_button" in your API response, stop and think about what you're really trying to communicate.

❌ Bad: UI-specific

{"widget": "red_button", "position": "top_right"}

✅ Good: Business context

{"urgency": "time_sensitive", "reason": "service_expires_today"}

Same goes for data presentation. Don't dictate tables and columns. Describe what you're serving:

❌ Bad: Prescriptive

{"display": "table", "columns": ["name", "email"]}

✅ Good: Descriptive

{
  "dataStructure": "collection",
  "businessProperties": {
    "identityField": "email",
    "operationalSummary": ["name", "lastActive"]
  }
}

9.2 Frontend Best Practices

Context informs your UI decisions but doesn't dictate them. When you see urgency: time_sensitive, that might become a prominent banner on web, a push notification on mobile, or red text with ⚠️ in a CLI. Each platform interprets business context through its own design language.

And remember: context is a enhancement, not a requirement. Your frontend should work fine without it.

9.3 Content Negotiation

Accept: application/hal+json; profile="https://hateoas-context.org/v1"

10. Migration Strategy

10.1 From Traditional HATEOAS

Before: Just links

{
  "_links": {
    "cancel": {"href": "/orders/123/cancel"}
  }
}

After: Links with context

{
  "_links": {
    "cancel": {
      "href": "/orders/123/cancel",
      "_context": {
        "availability": {
          "reason": "order_not_shipped"
        },
        "businessImpact": "reversible"
      }
    }
  }
}

10.2 From Frontend Business Logic

// Before: Business logic in frontend
if (order.status === 'pending' && !order.shipped) {
  showCancelButton();
}
if (user.accountType === 'premium' || user.trialEndsAt > Date.now()) {
  hideUpgradePrompt();
}

// After: Context from backend
if (links.cancel?._context?.available) {
  showCancelButton();
}
if (links.upgrade?._context?.urgency === 'high') {
  showUpgradePrompt();
}

11. Security and Privacy Considerations

11.1 Information Disclosure

Business context must not reveal sensitive information:

❌ Bad: Reveals too much

{
  "_context": {
    "unavailable_reason": "user_456_has_higher_bid"
  }
}

✅ Good: Generic business rule

{
  "_context": {
    "unavailable_reason": "already_claimed"
  }
}

11.2 Permission-Aware Context

Context should reflect what the user is allowed to know. Note that this doesn't violate progressive enhancement - the core functionality remains available without context, but authorized users receive richer business information:

Regular user sees:

{
  "_links": {
    "approve_expense": {
      "href": "/expenses/123/approve"
    }
  },
  "_context": {
    "availability": {
      "available": false,
      "reason": "insufficient_privileges"
    }
  }
}

Financial manager sees:

{
  "_links": {
    "approve_expense": {
      "href": "/expenses/123/approve"
    }
  },
  "_context": {
    "availability": {
      "available": false,
      "reason": "requires_senior_approval",
      "pendingApprover": "CFO",
      "businessContext": "amount_exceeds_delegation_limit"
    }
  }
}

The key principle: base functionality (links and data) remains consistent, but business context is tailored to the user's role and permissions.

12. Real-World Case Study

12.1 E-Commerce Checkout Process

Here's what happens without HATEOAS+Context:

// Frontend (duplicated business logic)
function getShippingOptions(cart) {
  if (cart.items.some(item => item.hazmat)) {
    return ['ground'];
  }
  if (cart.total > 100) {
    return ['ground', 'express', 'overnight', 'free_standard'];
  }
  return ['ground', 'express', 'overnight'];
}

With HATEOAS+Context:

{
  "cart": {
    "total": 150.00,
    "_links": {
      "shipping_options": {
        "href": "/cart/shipping-options",
        "_context": {
          "availableOptions": {
            "ground": {
              "businessReason": "always_available",
              "estimatedDays": 5
            },
            "express": {
              "businessReason": "standard_offering",
              "estimatedDays": 2
            },
            "overnight": {
              "businessReason": "premium_service",
              "estimatedDays": 1
            },
            "free_standard": {
              "businessReason": "qualified_by_order_total",
              "qualification": "order_exceeds_100",
              "estimatedDays": 7
            }
          },
          "restrictions": [],
          "recommendation": "free_standard"
        }
      }
    }
  }
}

13. Conclusion

HATEOAS+Context fixes what's broken about HATEOAS. Your frontend developers stop duplicating business logic. Your backend developers stop pretending they don't know why certain actions matter. Everyone stays in their lane, but now they're actually communicating.

The magic happens when you realize that "this subscription expires in 3 days" is business context, while "show a red warning banner" is a UI decision. Once you internalize that distinction, building APIs that frontends actually want to use becomes straightforward.

The backend owns business logic. The frontend owns presentation. Business context is the bridge between them.


Version: 1.0 Status: Draft Authors: [Your Name] License: CC BY 4.0

Appendix A: Quick Reference

Business Context vs UI Instructions

❌ Avoid (UI Instruction) ✅ Prefer (Business Context)
"display": "red_button" "urgency": "time_sensitive"
"columns": ["name", "email"] "businessProperties": {"identityField": "email", "operationalSummary": ["name"]}
"position": "top_right" "importance": "primary"
"use_modal": true "processType": "user_input_required"
"icon": "trash" "businessImpact": "permanent_data_loss"

Context Categories

  1. Availability: Why something can/cannot be done
  2. Impact: What happens if this action is taken
  3. Urgency: Time-sensitive business factors
  4. Requirements: What's needed for this action
  5. Relationships: How this connects to other concepts
  6. State: Current business state and transitions

Appendix B: JSON Schema

A formal JSON Schema for validating HATEOAS+Context responses is included in this repository as hateoas-context-v1.json.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors