Transport Bindings
Transport Bindings
Section titled “Transport Bindings”Protocol mapping for HTTP and message queues
Overview
Section titled “Overview”Vend is transport-agnostic. This document specifies conventions for common transports to ensure interoperability.
HTTP bindings follow RFC 9110 (HTTP Semantics) for status codes, methods, and headers.
Request
Section titled “Request”| Aspect | Convention |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Endpoint | Service-defined (e.g., /vend, /rpc, /api) |
| Body | JSON-encoded Vend request |
Response
Section titled “Response”| Aspect | Convention |
|---|---|
| Content-Type | application/json |
| Status Code | Semantic HTTP status code matching error |
| Body | JSON-encoded Vend response |
Status Codes
Section titled “Status Codes”HTTP status codes MUST reflect the Vend error. Success responses return 200 OK; error responses return the appropriate HTTP status code:
HTTP/1.1 404 Not FoundContent-Type: application/json
{ "protocol": { "name": "vend", "version": "0.1.0" }, "id": "req_123", "result": null, "errors": [{ "code": "NOT_FOUND", "message": "Order not found", "retryable": false }]}See Errors for the complete mapping of error codes to HTTP status codes.
Common status codes:
| Status | Error Codes |
|---|---|
200 OK | Success (no errors) |
400 Bad Request | PARSE_ERROR, INVALID_REQUEST, INVALID_ARGUMENTS |
401 Unauthorized | UNAUTHORIZED |
403 Forbidden | FORBIDDEN |
404 Not Found | NOT_FOUND, FUNCTION_NOT_FOUND |
429 Too Many Requests | RATE_LIMITED |
500 Internal Server Error | INTERNAL_ERROR |
502 Bad Gateway | DEPENDENCY_ERROR |
503 Service Unavailable | UNAVAILABLE, SERVER_MAINTENANCE, FUNCTION_MAINTENANCE |
Exception: Batch requests return 200 OK with per-operation status codes in the response body. See Batch Extension.
Header Mapping
Section titled “Header Mapping”HTTP headers provide metadata for infrastructure components (load balancers, proxies, observability tools) without parsing the JSON body.
Request Headers
Section titled “Request Headers”| Header | Maps To | Description |
|---|---|---|
X-Vend-Request-Id | id | Request identifier |
X-Vend-Trace-Id | tracing extension | Distributed trace ID |
X-Vend-Span-Id | tracing extension | Current span ID |
X-Vend-Parent-Span-Id | tracing extension | Parent span ID |
X-Vend-Caller | context.caller | Calling service name |
Trace headers map to the Tracing Extension, not context.
Response Headers
Section titled “Response Headers”| Header | Maps To | Description |
|---|---|---|
X-Vend-Request-Id | id | Echoed request identifier |
X-Vend-Duration-Ms | meta.duration | Processing time in milliseconds |
X-Vend-Node | meta.node | Handling node identifier |
RateLimit-Limit | meta.rate_limit.limit | Rate limit ceiling |
RateLimit-Remaining | meta.rate_limit.remaining | Remaining requests |
RateLimit-Reset | meta.rate_limit.resets_in | Seconds until window reset |
RateLimit-Policy | — | Policy identifier (optional) |
Rate limit headers follow IETF draft-ietf-httpapi-ratelimit-headers.
- Headers are OPTIONAL — the JSON body is authoritative
- Servers SHOULD set headers for observability
- Clients SHOULD set trace headers for propagation
- When headers and body conflict, body takes precedence
Format differences: Headers use string representations (milliseconds for duration). The JSON body uses structured objects. Servers MUST convert between formats when mapping headers to body.
Example
Section titled “Example”POST /vend HTTP/1.1Host: orders-api.internalContent-Type: application/jsonX-Vend-Request-Id: req_xyz789X-Vend-Trace-Id: tr_8f3a2b1cX-Vend-Span-Id: sp_4d5e6fX-Vend-Caller: checkout-service
{ "protocol": { "name": "vend", "version": "0.1.0" }, "id": "req_xyz789", "call": { "function": "orders.create", "version": "1", "arguments": { "customer_id": 42 } }, "context": { "caller": "checkout-service" }, "extensions": [ { "urn": "urn:vnd:ext:deadline", "options": { "value": 5, "unit": "second" } }, { "urn": "urn:vnd:ext:tracing", "options": { "trace_id": "tr_8f3a2b1c", "span_id": "sp_4d5e6f" } } ]}HTTP/1.1 200 OKContent-Type: application/jsonX-Vend-Request-Id: req_xyz789X-Vend-Duration-Ms: 127X-Vend-Node: orders-api-2RateLimit-Limit: 1000RateLimit-Remaining: 847RateLimit-Reset: 45
{ "protocol": { "name": "vend", "version": "0.1.0" }, "id": "req_xyz789", "result": { "order_id": 12345 }, "extensions": [ { "urn": "urn:vnd:ext:tracing", "data": { "trace_id": "tr_abc123", "span_id": "span_server_001", "duration": { "value": 127, "unit": "millisecond" } } } ], "meta": { "node": "orders-api-2", "rate_limit": { "limit": 1000, "remaining": 847, "resets_in": { "value": 45, "unit": "second" } } }}Timeouts
Section titled “Timeouts”HTTP clients SHOULD configure connection and read timeouts:
| Timeout | Recommendation |
|---|---|
| Connection | 5 seconds |
| Read | Vend deadline + buffer (e.g., deadline + 1s) |
The read timeout SHOULD exceed the Vend deadline to allow the server to return a DEADLINE_EXCEEDED error rather than the connection being cut.
Keep-Alive
Section titled “Keep-Alive”HTTP/1.1 connections SHOULD use keep-alive for efficiency. HTTP/2 is RECOMMENDED for high-throughput scenarios as it provides:
- Connection multiplexing
- Header compression
- Reduced latency
Message Queues
Section titled “Message Queues”Overview
Section titled “Overview”Message queues enable asynchronous request processing. Common implementations: RabbitMQ, Redis Streams, Amazon SQS.
Message Structure
Section titled “Message Structure”| Aspect | Convention |
|---|---|
| Body | JSON-encoded Vend request |
| Content-Type | application/json |
| Correlation ID | Vend id field |
| Reply-To | Queue for response (if expecting reply) |
Property Mapping
Section titled “Property Mapping”Request Message Properties
Section titled “Request Message Properties”| Property | Maps To | Description |
|---|---|---|
correlation_id | id | Request identifier |
reply_to | — | Response queue name |
expiration | deadline extension | Message TTL in milliseconds |
headers.trace_id | tracing extension | Distributed trace ID |
headers.span_id | tracing extension | Current span ID |
headers.caller | context.caller | Calling service |
Trace headers map to the Tracing Extension, not context.
Response Message Properties
Section titled “Response Message Properties”| Property | Maps To | Description |
|---|---|---|
correlation_id | id | Echoed request identifier |
headers.duration_ms | meta.duration | Processing time |
headers.node | meta.node | Handling node |
Deadline Handling
Section titled “Deadline Handling”Message TTL SHOULD reflect the Vend deadline:
message_ttl = deadline_value (converted to ms)When a message expires before processing:
- Queue MAY move it to dead letter queue
- Consumer SHOULD NOT process expired messages
- If processed, server SHOULD return
DEADLINE_EXCEEDED
Request-Response Pattern
Section titled “Request-Response Pattern”For standard requests expecting responses:
- Client publishes request to service queue
- Client waits on reply queue (from
reply_to) - Server processes request
- Server publishes response to reply queue
- Client receives response, matched by
correlation_id
┌──────────┐ request ┌───────────────┐│ Client │ ───────────────► │ Service Queue │└──────────┘ └───────────────┘ ▲ │ │ ▼ │ ┌───────────┐ │ response │ Server │ └─────────────────────── │ │ (reply queue) └───────────┘Dead Letter Handling
Section titled “Dead Letter Handling”Failed messages SHOULD be routed to a dead letter queue when:
- Message expires (TTL exceeded)
- Processing fails after max retries
- Message cannot be parsed
Dead letter queues enable:
- Debugging failed requests
- Manual retry/reprocessing
- Alerting on failure patterns
Example: RabbitMQ
Section titled “Example: RabbitMQ”Publishing Request:
$channel->basic_publish( new AMQPMessage( json_encode($vendRequest), [ 'content_type' => 'application/json', 'correlation_id' => $vendRequest['id'], 'reply_to' => 'responses.checkout-service', 'expiration' => '5000', // 5 seconds 'headers' => new AMQPTable([ 'trace_id' => $tracingExtension['options']['trace_id'] ?? null, 'span_id' => $tracingExtension['options']['span_id'] ?? null, 'caller' => 'checkout-service', ]), ] ), '', 'orders-api');Publishing Response:
$channel->basic_publish( new AMQPMessage( json_encode($vendResponse), [ 'content_type' => 'application/json', 'correlation_id' => $vendResponse['id'], 'headers' => new AMQPTable([ 'duration_ms' => 127, 'node' => 'orders-api-2', ]), ] ), '', $replyTo);Unix Sockets
Section titled “Unix Sockets”For local inter-process communication:
| Aspect | Convention |
|---|---|
| Protocol | Stream socket |
| Framing | Length-prefixed JSON |
| Path | Service-defined (e.g., /var/run/vend/orders.sock) |
Message Framing
Section titled “Message Framing”Each message is prefixed with a 4-byte big-endian length:
┌──────────────┬─────────────────────┐│ Length (4B) │ JSON Payload │└──────────────┴─────────────────────┘This allows efficient message boundary detection without delimiter scanning.
Implementation Notes
Section titled “Implementation Notes”Content Negotiation
Section titled “Content Negotiation”Vend uses JSON exclusively. Servers SHOULD reject requests with unsupported content types:
HTTP/1.1 415 Unsupported Media TypeContent-Type: application/json
{ "protocol": { "name": "vend", "version": "0.1.0" }, "id": null, "result": null, "errors": [{ "code": "INVALID_REQUEST", "message": "Content-Type must be application/json", "retryable": false }]}Request Size Limits
Section titled “Request Size Limits”Servers SHOULD enforce request size limits:
| Limit | Recommendation |
|---|---|
| Maximum request body | 1 MB |
| Maximum header size | 8 KB |
Exceeded limits return 413 Payload Too Large (HTTP) or reject the message (queues).
Compression
Section titled “Compression”Compression is handled at the transport layer:
- HTTP: Use standard
Accept-Encoding/Content-Encodingheaders (gzip, br) - Queues: Configure at broker level or use compressed message body
Vend does not specify compression — it is an infrastructure concern.
Authentication
Section titled “Authentication”Authentication is an implementation concern handled at the transport layer. Vend does not specify authentication mechanisms but provides guidance for common patterns.
HTTP Authentication
Section titled “HTTP Authentication”Bearer Tokens
Section titled “Bearer Tokens”Use standard Authorization header for token-based authentication per RFC 6750 (OAuth 2.0 Bearer Token Usage):
POST /vend HTTP/1.1Host: orders-api.internalContent-Type: application/jsonAuthorization: Bearer eyJhbGciOiJIUzI1NiIs...Servers validate the token before processing the Vend request. Invalid tokens return transport-level errors:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer realm="vend-api"
{ "error": "invalid_token", "error_description": "Token has expired"}Note: This is a transport-level error, not a Vend error. The request never reached the Vend handler.
API Keys
Section titled “API Keys”For service-to-service authentication:
POST /vend HTTP/1.1Host: orders-api.internalContent-Type: application/jsonX-API-Key: sk_live_abc123xyzMutual TLS (mTLS)
Section titled “Mutual TLS (mTLS)”For internal service authentication, use mutual TLS per RFC 8446 (TLS 1.3) at the transport layer. The client presents a certificate that identifies the calling service:
┌─────────────┐ mTLS ┌─────────────┐│ Service A │ ◄────────────► │ Service B ││ (client) │ cert: A │ (server) │└─────────────┘ └─────────────┘With mTLS, the server can populate context.caller from the client certificate’s Common Name (CN) or Subject Alternative Name (SAN).
Message Queue Authentication
Section titled “Message Queue Authentication”For message queues, authentication typically occurs at connection time:
- RabbitMQ: Username/password or x509 certificates
- Amazon SQS: IAM roles and policies
- Redis Streams: ACL authentication
The queue broker validates credentials before allowing message publication or consumption.
Authorization Context
Section titled “Authorization Context”After transport-level authentication, authorization data should be propagated via context:
{ "context": { "caller": "checkout-service", "tenant_id": "tenant_acme", "user_id": "user_42", "scopes": ["orders:read", "orders:write"] }}This enables:
- Multi-tenant isolation (filter by
tenant_id) - User-level authorization (validate
user_idhas access) - Scope-based access control (check
scopesfor permission)
Internal vs External APIs
Section titled “Internal vs External APIs”| Aspect | Internal (service-to-service) | External (client-facing) |
|---|---|---|
| Authentication | mTLS, API keys | OAuth 2.0, JWT |
| Trust level | High (verified service) | Low (validate everything) |
| Rate limiting | Per-service quotas | Per-user/tenant quotas |
| Context source | Propagated from upstream | Extracted from token |
Security Recommendations
Section titled “Security Recommendations”- Always use TLS — Never send Vend requests over unencrypted connections
- Validate at the edge — Authenticate external requests at API gateway
- Propagate identity — Pass authenticated identity via context
- Principle of least privilege — Scope service permissions narrowly
- Rotate credentials — Use short-lived tokens where possible