8.10 The proxy and sms services

Before writing a custom handler for an external provider, check whether the runtime already owns the shape you need. proxy covers fixed forwarding with host-injected credentials; sms defines a stable provider-independent SMS surface over a swappable adapter.

proxy: forward without exposing the key

{ "path": "/stripe", "service": "proxy", "config": {
    "access": { "read": "A", "write": "A", "invoke": "A" },
    "target": "https://api.stripe.com",
    "inject": "infra:stripe-key"
} }

GET /stripe/v1/charges?limit=3 becomes GET https://api.stripe.com/v1/charges?limit=3. The target fixes the upstream host; callers cannot turn the mount into an open proxy. Client headers are forwarded except for Host, the caller's RS2 Authorization/cookie credentials, and hop-by-hop proxy headers; the upstream response comes back unchanged.

inject adds authentication immediately before the request leaves the host:

  • "infra:<name>" uses an operator-managed credential (recommended for operator-owned keys).
  • An inline strategy can draw from a tenant secret, for example {"auth":"bearer","token":"secret:apiKey"} when the mount grants "secrets": ["apiKey"].

Strategies include bearer, named header, basic auth, query parameter, HMAC, and AWS SigV4. HMAC and SigV4 materialize the body to sign it; the simpler strategies leave streaming bodies untouched. In every case, the secret is absent from the forwarded tenant config and never enters guest code.

Use proxy when the external API should remain visible as itself. Use a wrapper or custom service when you need to present a different contract.

sms: one surface, replaceable providers

{ "path": "/sms", "service": "sms", "config": {
    "access": { "read": "A", "invoke": "A" },
    "store": { "adapter": "code:twilio@<version>" }
} }

The tenant-facing conversation stays fixed:

Request Response
POST /sms/send with {to, body} 201 {id}
GET /sms/status/<id> The provider's delivery status, or 404

The adapter translates that canonical message to Twilio, SNS, or another provider. It is a resident JavaScript adapter, selected with store.adapter, and usually uses an httpOut grant with host-side credential injection. No first-party SMS provider ships in the node, so builtin: is rejected: deploy a code: adapter or reference an operator infra: that resolves to one.

This separation is the same one used by storage adapters: applications depend on a stable service contract, while provider choice remains configuration.


Previous: 8.9 Loadable storage adapters · Manual home · Next: 8.11 Manifests and catalogues →