Skip to content

Transport Bindings

Protocol mapping for HTTP and message queues


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.

AspectConvention
MethodPOST
Content-Typeapplication/json
EndpointService-defined (e.g., /vend, /rpc, /api)
BodyJSON-encoded Vend request
AspectConvention
Content-Typeapplication/json
Status CodeSemantic HTTP status code matching error
BodyJSON-encoded Vend response

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 Found
Content-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:

StatusError Codes
200 OKSuccess (no errors)
400 Bad RequestPARSE_ERROR, INVALID_REQUEST, INVALID_ARGUMENTS
401 UnauthorizedUNAUTHORIZED
403 ForbiddenFORBIDDEN
404 Not FoundNOT_FOUND, FUNCTION_NOT_FOUND
429 Too Many RequestsRATE_LIMITED
500 Internal Server ErrorINTERNAL_ERROR
502 Bad GatewayDEPENDENCY_ERROR
503 Service UnavailableUNAVAILABLE, SERVER_MAINTENANCE, FUNCTION_MAINTENANCE

Exception: Batch requests return 200 OK with per-operation status codes in the response body. See Batch Extension.


HTTP headers provide metadata for infrastructure components (load balancers, proxies, observability tools) without parsing the JSON body.

HeaderMaps ToDescription
X-Vend-Request-IdidRequest identifier
X-Vend-Trace-Idtracing extensionDistributed trace ID
X-Vend-Span-Idtracing extensionCurrent span ID
X-Vend-Parent-Span-Idtracing extensionParent span ID
X-Vend-Callercontext.callerCalling service name

Trace headers map to the Tracing Extension, not context.

HeaderMaps ToDescription
X-Vend-Request-IdidEchoed request identifier
X-Vend-Duration-Msmeta.durationProcessing time in milliseconds
X-Vend-Nodemeta.nodeHandling node identifier
RateLimit-Limitmeta.rate_limit.limitRate limit ceiling
RateLimit-Remainingmeta.rate_limit.remainingRemaining requests
RateLimit-Resetmeta.rate_limit.resets_inSeconds until window reset
RateLimit-PolicyPolicy 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.

POST /vend HTTP/1.1
Host: orders-api.internal
Content-Type: application/json
X-Vend-Request-Id: req_xyz789
X-Vend-Trace-Id: tr_8f3a2b1c
X-Vend-Span-Id: sp_4d5e6f
X-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 OK
Content-Type: application/json
X-Vend-Request-Id: req_xyz789
X-Vend-Duration-Ms: 127
X-Vend-Node: orders-api-2
RateLimit-Limit: 1000
RateLimit-Remaining: 847
RateLimit-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" }
}
}
}

HTTP clients SHOULD configure connection and read timeouts:

TimeoutRecommendation
Connection5 seconds
ReadVend 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.


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 enable asynchronous request processing. Common implementations: RabbitMQ, Redis Streams, Amazon SQS.

AspectConvention
BodyJSON-encoded Vend request
Content-Typeapplication/json
Correlation IDVend id field
Reply-ToQueue for response (if expecting reply)
PropertyMaps ToDescription
correlation_ididRequest identifier
reply_toResponse queue name
expirationdeadline extensionMessage TTL in milliseconds
headers.trace_idtracing extensionDistributed trace ID
headers.span_idtracing extensionCurrent span ID
headers.callercontext.callerCalling service

Trace headers map to the Tracing Extension, not context.

PropertyMaps ToDescription
correlation_ididEchoed request identifier
headers.duration_msmeta.durationProcessing time
headers.nodemeta.nodeHandling node

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

For standard requests expecting responses:

  1. Client publishes request to service queue
  2. Client waits on reply queue (from reply_to)
  3. Server processes request
  4. Server publishes response to reply queue
  5. Client receives response, matched by correlation_id
┌──────────┐ request ┌───────────────┐
│ Client │ ───────────────► │ Service Queue │
└──────────┘ └───────────────┘
▲ │
│ ▼
│ ┌───────────┐
│ response │ Server │
└─────────────────────── │ │
(reply queue) └───────────┘

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

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
);

For local inter-process communication:

AspectConvention
ProtocolStream socket
FramingLength-prefixed JSON
PathService-defined (e.g., /var/run/vend/orders.sock)

Each message is prefixed with a 4-byte big-endian length:

┌──────────────┬─────────────────────┐
│ Length (4B) │ JSON Payload │
└──────────────┴─────────────────────┘

This allows efficient message boundary detection without delimiter scanning.


Vend uses JSON exclusively. Servers SHOULD reject requests with unsupported content types:

HTTP/1.1 415 Unsupported Media Type
Content-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
}]
}

Servers SHOULD enforce request size limits:

LimitRecommendation
Maximum request body1 MB
Maximum header size8 KB

Exceeded limits return 413 Payload Too Large (HTTP) or reject the message (queues).

Compression is handled at the transport layer:

  • HTTP: Use standard Accept-Encoding/Content-Encoding headers (gzip, br)
  • Queues: Configure at broker level or use compressed message body

Vend does not specify compression — it is an infrastructure concern.


Authentication is an implementation concern handled at the transport layer. Vend does not specify authentication mechanisms but provides guidance for common patterns.

Use standard Authorization header for token-based authentication per RFC 6750 (OAuth 2.0 Bearer Token Usage):

POST /vend HTTP/1.1
Host: orders-api.internal
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Servers validate the token before processing the Vend request. Invalid tokens return transport-level errors:

HTTP/1.1 401 Unauthorized
WWW-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.

For service-to-service authentication:

POST /vend HTTP/1.1
Host: orders-api.internal
Content-Type: application/json
X-API-Key: sk_live_abc123xyz

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).

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.

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_id has access)
  • Scope-based access control (check scopes for permission)
AspectInternal (service-to-service)External (client-facing)
AuthenticationmTLS, API keysOAuth 2.0, JWT
Trust levelHigh (verified service)Low (validate everything)
Rate limitingPer-service quotasPer-user/tenant quotas
Context sourcePropagated from upstreamExtracted from token
  1. Always use TLS — Never send Vend requests over unencrypted connections
  2. Validate at the edge — Authenticate external requests at API gateway
  3. Propagate identity — Pass authenticated identity via context
  4. Principle of least privilege — Scope service permissions narrowly
  5. Rotate credentials — Use short-lived tokens where possible