Skip to main content

Public endpoints and domains

A service can be reached in two ways: privately, from inside your project, or publicly, from the internet. This page shows you the URL each one gives you and how the encryption is handled. It also states plainly what does not exist yet. There is no access control on a published service, and there are no custom domains.

Private by default

A new service is internal: reachable only from inside the platform, not from the internet. Every service always has an internal URL of this shape:

http://<private-hostname>

That address only resolves inside the platform's own network, so typing it into your browser does nothing. Other workloads in your project — agents, functions, other services — call it directly. It appears as internal_url on the service object and in the console's URL block.

Internal does not mean authenticated

"Internal" removes your service from the internet. It does not check who is calling. Any workload that can reach the platform's shared gateway can reach an internal service by addressing it by name. If your service needs to know its callers, add authentication in your own application code.

Publish to the internet

Turn on publish and the platform gives your service a public HTTPS URL:

https://<name>-<project-short>.apps.codyhill.dev

The platform builds that hostname for you. It joins two parts with a dash: your service name and your project short id. Both sit under the shared apps.codyhill.dev domain. So a service named checkout-api in project acme becomes https://checkout-api-acme.apps.codyhill.dev. You cannot choose a different hostname with this flag — see Custom domains below.

Publish is a single boolean, settable at create time or later:

platformctl serverless update checkout-api --publish

Publishing is replaced as a pair, enabled and host together, so --publish-host refuses to travel without --publish rather than taking your service off the internet as a side effect. The platform ignores host anyway — see Custom domains.

Publishing does not finish while you wait for the response. Ask the platform for the service every few seconds and watch for the external URL to appear:

platformctl serverless get checkout-api

You should see, once publishing completes:

NAME STATE PUBLISHED URL
checkout-api ready yes https://checkout-api-acme.apps.codyhill.dev

The URL column prefers external_url and falls back to the internal address, so the row changing to an https:// address is itself the signal that publishing finished. Add -o json for every field.

Key operational details:

  • external_url is filled in only once the URL actually works. The platform will not report a public address until it has checked that the hostname answers over valid TLS. Until then the field is empty.
  • The Exposed condition holds readiness back. For a published service, state does not read ready — and ready stays false — while the public URL is still being wired up. The service's message then carries the Exposed condition's own words naming the step it is waiting on — see troubleshooting.

To take a service off the internet again, run platformctl serverless update checkout-api --publish=false, PATCH {"publish": {"enabled": false}}, or clear the checkbox in Edit. Deleting a service releases its public hostname.

Published means open to everyone

Publishing adds no access control at all

Publishing puts your service on the public internet with no gate in front of it. Anyone who knows the URL can call it, from anywhere. The platform does not check identities, tokens, signatures, or source IP addresses on the way in.

This surprises people who come from other clouds. publish has exactly two settings: enabled and host. The platform ignores host. There is no third field and no separate API for any of the following:

What you may be looking forStatus on this platform
An "invoker" role, so only certain identities may call the service (like GCP Cloud Run's roles/run.invoker)Does not exist
Requiring a signed or authenticated request by default (like AWS Lambda function URLs' AWS_IAM auth type)Does not exist
A source-IP allowlist, a firewall rule, or a web application firewall that inspects traffic before it reaches youDoes not exist
An API key or shared secret checked by the platformDoes not exist

The same is true of a published function. Its public URL is open too, which is why the function quickstart calls one with a plain curl and no credential.

What to do instead

  1. Publish it through the API Gateway instead of this flag. That is a separate path to the internet, and it is the one with the controls this flag lacks: a required credential (a token from your identity provider, or a platform-issued API key), an address allow list, a rate limit, and your own domain. Leave publish.enabled off and create a gateway endpoint pointing at the service — see Publish an endpoint. Everything below still applies to this flag itself.
  2. Keep it internal if it does not need to be public. An internal service is off the internet. Callers inside the platform are still not checked, but nobody outside can reach it at all. This is the right answer for admin tools, back-office jobs, and anything a browser never touches directly.
  3. Authenticate in your own application code. On every request, check an Authorization header, a shared secret, or a signed request inside your handler. Store the secret with Secrets Manager and read it from the environment. Do not hardcode it. This is the only real gate available for a published service today.
  4. Assume the URL is public knowledge. The hostname comes from your service name and project short ID, so anyone can guess it. A secret URL is not a security control.
  5. For an agent chat widget specifically, the embeddable chat path has a control the serverless path lacks. It uses an embed key plus a list of web page addresses allowed to load the widget. The server checks both. That covers embedding an agent in a web page, not calling a serverless service.

Ensure appropriate authentication controls are implemented on published public endpoints. See Security overview for details.

How TLS is handled

TLS is the encryption behind https:// and the browser padlock, and the platform handles all of it. One shared wildcard certificate covers every *.apps.codyhill.dev hostname. There is nothing for you to request, upload, or renew. There is also no option to bring your own certificate today.

Test a revision directly with tags

When you split traffic between revisions, you can put a tag on a traffic target. A tag is a short label such as canary. A tagged revision gets its own hostname, so you can send test requests straight to the new revision without touching the percentage split. That URL appears in the url field of the target inside active_traffic — the split actually being served, as distinct from the traffic you requested — and on the console's Revisions tab. See deploy a service for the traffic-split API.

Custom domains

Not through this flag. With publish.enabled, your public URL is always https://<name>-<project-short>.apps.codyhill.dev. One sharp edge worth naming: the API accepts and stores a publish.host field and the platform ignores it, so do not set it expecting a custom hostname.

To serve on a domain you own, publish the service through the API Gateway instead. You add the domain to your project, create one CNAME record, and publish an endpoint on it; the certificate is handled for you. Subdomains only — api.example.com, not example.com and not *.example.com.

Summary

SettingValue
Default visibilityInternal (publish.enabled: false)
Internal URLhttp://<private-hostname> — always present, plain HTTP, unauthenticated
Public URLhttps://<name>-<project-short>.apps.codyhill.dev — appears in external_url only once live
Caller authenticationNone, published or internal. No invoker role, no signed-request mode, no IP allowlist — authenticate in your own code
Hostname choiceNone — always computed from service name + project short ID; publish.host is ignored
TLSAutomatic, shared wildcard certificate; HTTPS on published services only
Custom domainsConfigurable via platform Gateway
ReadinessExposed condition keeps ready at false until the public URL answers over valid TLS

Next steps