A function URL by default: free, up to 15 minutes, nothing to keep in sync. API Gateway from day one when you sell an API, need a private one, or need what only it does at the door. Often both: the partner API on API Gateway, the app's own endpoints on function URLs, the same code behind them.
When to pick which
Pick a function URL when
- It's your own app's API, or service-to-service calls signed with IAM.
- Requests can take longer than 30 seconds: reports, LLM calls, file processing. A function URL waits up to 15 minutes.
- You stream from Node.js outside a VPC, up to 200 MB per response.
- A webhook from one provider, whose signature you check in code.
Pick API Gateway when
- You sell or meter an API for third parties: per-customer keys, quotas and throttling tiers (REST API).
- Bad requests must be rejected before your code runs, against a published contract (REST API validation).
- The API must not be public: callers inside a VPC or on premises (REST private API).
- No Lambda in the path: a webhook straight into SQS, EventBridge or Step Functions (HTTP API).
- JWT checks at the door, routes over many functions under one domain, client certificates (mTLS), caching or canary releases.
Both, when
- The partner API on API Gateway, with keys, quotas and validation; the app's own endpoints on function URLs. The same code behind both.
- Then lock the function URL behind the partner API down (IAM auth, or no URL at all): a public URL with auth NONE bypasses everything API Gateway enforces.
Questions that change the answer
Will anyone outside your team call this API, and will you bill or limit them per customer? That's API Gateway's REST API.
Can any request take longer than 30 seconds? An HTTP API stops at 30, a REST API at 29 by default.
Must the API stay off the public internet? Function URLs are public only.
Does the domain matter to clients? Put it on a Worker or CloudFront from the first deploy, so a later move is an origin change, not a new URL.
The short version
| Function URL | HTTP API | REST API | |
|---|---|---|---|
| Price on top of Lambda (Sydney) | $0 | $1.29 per million requests | $3.50 per million |
| At 100M requests a month | $0 | $129 | $350 |
| Longest request | 15 minutes | 30 seconds, fixed | 29 seconds by default, more on request for regional and private APIs |
| Streaming | Yes, up to 200 MB (not inside a VPC) | No | Yes, since November 2025, in its own format |
| Event format | Payload 2.0 | Payload 2.0: same handler | 1.0: needs an adapter |
| Custom domain | No: a Worker or CloudFront in front | Yes | Yes |
| Auth at the door | IAM or none | IAM, JWT, Lambda authorizer | IAM, Cognito, Lambda authorizer, API keys |
| Per-customer keys, quotas, throttling | No: reserved concurrency limits everyone together | No | Yes |
| Request validation | No | No | Yes |
| WAF | No: in front | No: in front | Attached directly |
| Private API (VPC only) | No: public only | No | Yes |
| Straight into SQS, EventBridge, Step Functions | No: your function always runs | Yes, a fixed list | Yes, any AWS service |
| Caching | No | No | Yes |
Price doesn't decide this one. At 100 million requests a month the dearest door costs $350, small next to what such a product spends on compute and people. Features and limits decide it.
Why a function URL is my default
Most APIs I build are the app's own: a front end and its backend, or one service calling another. For those, everything API Gateway adds is configuration to keep in sync with the code (routes, stages, deployments, integrations) for features nobody uses. A function URL is one HTTPS endpoint on one function, created with it in Pulumi, and it waits up to 15 minutes where API Gateway stops at about 30 seconds.
What it doesn't have is the edge: no custom domain, no WAF, no caching, no per-client rate limit. Its only throttle is reserved concurrency, which returns 429 above the limit for everyone at once. So anything public gets a door in front: a Cloudflare Worker (domain, rate limits, bot rules, streaming passes through), or CloudFront when everything must stay on AWS.
When I start on API Gateway from day one
- You sell the API. Partners get their own keys, quotas and throttling tiers, maybe a developer portal. That's the REST API's usage plans, and a function URL has nothing like them.
- The contract is enforced at the door. Request validation against a schema, so bad payloads never reach your code. REST only.
- The API must stay private. Callers in a VPC, or on premises over Direct Connect. A function URL is reachable from the public internet only; a REST private API isn't.
- There's no code to run. A webhook that goes straight into a queue or a workflow: an HTTP API integrates with SQS, EventBridge, Kinesis and Step Functions directly, so you don't own a function that only forwards.
- The door does real work. JWT checks off the function (HTTP API), many routes over many functions under one domain, client certificates for B2B (mTLS), caching, canary releases (REST).
WebSockets are the exception: API Gateway has a WebSocket API, but for live shared state I use a Durable Object instead (the live auction shows why).
Switching later
To an HTTP API, the handler doesn't change. Both send payload 2.0. What changes is around it: the 30-second ceiling, no streaming, and the CORS settings that move from the URL to the API (set them in one place, or browsers get two Access-Control-Allow-Origin headers).
To a REST API, it does. A REST API sends the older 1.0 event (httpMethod, multiValueHeaders, no cookies field) and expects a 1.0 response, so the handler needs a thin adapter, or two small entry points around the same logic. Streaming changes format too.
Keep the address stable from day one. A function URL's own address is a long lambda-url hostname. If clients ever call it directly, moving is a breaking change. Put the domain on a Worker or on CloudFront from the first deploy, and the move to API Gateway, or to Lambda Web Functions when they reach your region, is one origin change that clients never see.
One trap with CloudFront in front: with origin access control on the function URL, every POST and PUT must carry a SHA-256 hash of its body in a header, because Lambda doesn't accept unsigned payloads that way. Browsers and partners won't send it. Either use a Worker, or leave the URL's auth at none and check a secret header that only CloudFront adds.
Lambda Web Functions, a third option to watch
Launched on 12 October 2026 and not in Sydney yet: a Node.js web server (Express, Hono) on Lambda with its own HTTPS endpoint, many requests per environment, streaming, 15 minutes per request and a per-endpoint rate limit. No custom domain or WAF of its own either, so the same door goes in front. For an app's own API it could replace the function URL once it reaches your region; it doesn't replace API Gateway's keys, quotas and validation. Lambda, Web Functions or Workers? compares them.
Sources, checked on 25 October 2026: function URLs or API Gateway (AWS), function URL configuration and throttling, payload format 1.0 and 2.0, REST API response streaming, REST and HTTP APIs compared, API Gateway quotas, API Gateway pricing, CloudFront origin access control for function URLs, Lambda Web Functions launch.