Service bindings
A service binding lets server-side code in one service call another service directly, without exposing the target service to public traffic.
Bindings are how services find each other internally. The binding simultaneously grants access to the caller and generates the URL the caller's code uses, so there is no public route or hardcoded hostname involved.
You declare a binding on the caller service. The caller names the target service and the environment variable that receives the generated URL.
{
"services": {
"orders": {
"root": "services/orders/",
"framework": "express",
"bindings": [
{
"type": "service",
"service": "inventory",
"format": "url",
"env": "INVENTORY_URL"
}
]
},
"inventory": {
"root": "services/inventory/",
"framework": "fastapi",
"entrypoint": "main:app"
}
},
"rewrites": [{ "source": "/api/(.*)", "destination": { "service": "orders" } }]
}The top-level rewrite is the only public entry point. A request from the public internet can reach orders, but Vercel never routes public traffic to inventory: it has no rewrite of its own, so it stays unreachable from the outside.
orders can still call inventory because it declares a binding to it. The binding grants that access and injects the target URL as INVENTORY_URL, which server-side code in orders reads to build request URLs:
await fetch(new URL('items/123', process.env.INVENTORY_URL));| Field | Required | Description |
|---|---|---|
type | Yes | Must be "service" for a service-to-service binding. |
service | Yes | Target service name from services. |
format | Yes | Must be "url". The generated value is an absolute URL base for the target service. |
env | Yes | Environment variable name that receives the generated URL. Vercel generates this value and injects into a service, you do not set it. |
The binding is the reachability grant. If orders declares a binding to inventory, it can call inventory. A service without a matching binding cannot derive the URL and fails to connect.
This grant is independent from public ingress. Adding a binding gives a service internal access to another service. It does not create a public route to the target, and a public rewrite to a service does not grant another service internal access to it.
Vercel injects the binding as a URL base, and the value is deployment-aware. This way, a preview deployment's orders service reaches that same preview deployment's inventory service, so you never reference a fixed hostname.
Internal calls also skip the public request pipeline. Firewall, Deployment Protection, the project's top-level middleware, and CDN request accounting do not apply to a service-to-service call.
A call over a binding is billed as a single service request, with no separate Edge Request or Fast Data Transfer charge. See Pricing and Limits for how billing works across services.
Bindings are available to a service's functions at runtime only. They do not resolve during builds, and code running in middleware cannot call another service over a binding. Make service-to-service calls from a function instead.
Was this helpful?