Skip to main content

Telnyx Compute: Functions — Full Documentation

Complete page content for Functions (Compute section) of the Telnyx developer docs (https://developers.telnyx.com). This file: https://developers.telnyx.com/development/llms/compute-functions-llms-full-txt.md · Root index: https://developers.telnyx.com/llms.txt

Overview

Functions

Source: https://developers.telnyx.com/docs/edge-compute/overview.md
A function is the compute primitive of Telnyx Edge Compute: an ordinary HTTP server, packaged as a container, deployed to Telnyx’s global edge network, and served at its own public URL. You write a server that listens on PORT; one command builds and ships it.
index.ts
On its own, a function just echoes HTTP. What makes it useful is the platform it plugs into — key-value storage, durable per-entity state, object storage, the Telnyx API, and private networking — each declared as a binding and reachable from your handler. This page maps that platform; the rest of these docs go deep on each piece. Install the CLI, scaffold a function, ship it, and curl the live URL — about five minutes. HTTP is the only trigger today — there are no cron triggers. For scheduled work, call the function’s URL from an external scheduler, such as a GitHub Actions cron job.

The platform around your functions

Declare a binding in func.toml and it surfaces on env at runtime in TypeScript, or as REST and injected environment variables in every other language. See Bindings for the mechanics — these are the products worth reaching for. State and storage Globally distributed key-value storage with server-side TTL — an env binding in TypeScript, REST everywhere else. For caches, sessions, and feature flags. Beta — durable per-entity state and coordination: one instance per name, one call at a time. For counters, per-user state, and anything that needs a serialized owner. S3-compatible buckets for files and media — a separate product, reached over its S3 API from any language. Connect to Telnyx A pre-authenticated client for Voice, Messaging, and AI — or plain REST with the injected key. Answer calls, send messages, and run inference without managing credentials.

Languages

telnyx-edge new-func -l <language> scaffolds a project in TypeScript (ts), JavaScript (js), Go (go), Python (python), or Java (quarkus). Each language uses its own standard server contract — node:http, Go’s http.Handler, ASGI, Quarkus Funqy — documented in HTTP handler. The binding SDK (@telnyx/edge-runtime) — a typed env exposing a pre-authenticated Telnyx client, secrets, and KV namespaces — is TypeScript-only today. The other runtimes use the same features over REST and injected environment variables.

Where your code runs

Functions run on the infrastructure that carries Telnyx voice and messaging traffic: edge sites inside carrier facilities, close to end users rather than in generic cloud availability zones. Each region is backed by multiple independent sites across different providers, with automatic failover if a site goes down.

Resources

The entrypoint contract for each language. Environment variables, secrets, routing, and versions. Every telnyx-edge command and flag. Request timeouts, payload sizes, and quotas. Free tier plus usage-based rates for requests and CPU time. Ask questions and share what you build.

Get Started

Quickstart

Source: https://developers.telnyx.com/docs/edge-compute/quickstart.md
Deploy your first function end-to-end: install the CLI, authenticate, scaffold, ship, and prove it with curl. A function is a container running your own HTTP server on Telnyx infrastructure, reachable at a public URL. The code steps below are shown for every supported language — pick a tab. TypeScript is the default and the only one with the typed binding SDK today; the rest are fully supported for plain HTTP.

Prerequisites

  • A Telnyx account — sign up if you don’t have one.
  • The toolchain for your language: Node.js ≥ 18 (TypeScript/JavaScript), Go, Python 3, or Java with Maven.

1. Install the CLI

The telnyx-edge CLI ships as binaries on the GitHub releases page. Assets are version-stamped, and each tarball extracts into a versioned directory containing the binary.
Linux (amd64)
macOS (Apple silicon)
For Intel Macs use the macos-amd64 asset; Windows zips are on the same releases page. Verify the install:

2. Authenticate

Authenticate before creating a function — new-func registers the function with the platform, which requires credentials.
Credentials persist to ~/.telnyx-edge/config.toml. The CLI does not read a TELNYX_API_KEY environment variable — in CI, run auth api-key set as a setup step.

3. Create a function

new-func creates the function server-side, writes the assigned UUID into func.toml, and scaffolds a working project. Pass -l to pick the language:
TypeScript
JavaScript
Go
Python
Java
Every scaffold contains a func.toml that ties the directory to the registered function:
The entrypoint file and its contract differ by language — TypeScript and JavaScript run their own server; Go, Python, and Java hand you a handler and run the server for you. Here is the scaffolded entrypoint, condensed to a health check plus a default JSON response:
index.ts
index.js
handler.go
function/func.py
src/main/java/functions/Function.java
These are trimmed for the quickstart. HTTP handler has the full scaffold for each language, the exact entrypoint contract, and how request bodies and health probes work.

4. Ship

ship uploads the project, builds it, deploys it, and monitors the rollout (default timeout 5 minutes; --timeout to change). The output ends with the live URL:
Every function gets a URL of the form {func-name}-{func-id-prefix}.telnyxcompute.com — see Routes & Domains.

5. Call it

Use the URL ship printed. Every scaffold answers a GET with the same default response:
Sending request bodies differs by contract — the TypeScript, JavaScript, Go, and Python scaffolds read the raw body, while the Java (Funqy) scaffold is JSON-in, JSON-out. See HTTP handler for each. The function is live. Iterate by editing the entrypoint and running telnyx-edge ship again — each successful ship creates an immutable revision you can roll back to.

Next Steps

  • Bindings — pre-authenticated Telnyx client, secrets, and KV on env.
  • KV quick start — persist data across requests from your function.

Best Practices

Best Practices

Source: https://developers.telnyx.com/docs/edge-compute/best-practices.md
An Edge Compute function is a real container running your own HTTP server — not a per-request sandbox. Most of the practices below follow from that model: module scope runs once per container, an escaped exception kills a process, and state has to live somewhere other than the container. Code samples are TypeScript. The same principles apply in every runtime; the binding SDK (@telnyx/edge-runtime) is TypeScript-only today, so other languages use environment variables and the REST APIs where a binding is shown.

Configuration

Keep Secrets Out of Code

Store credentials as secrets — the CLI takes the key and value as positional arguments:
Every secret is injected into your functions as a plain environment variable, so this works in any language:
TypeScript projects that declare a [[secrets]] binding in func.toml can also read it through env.SECRETS.get("<handle>"), which telnyx-edge types type-checks against the declared handles. Both surfaces are live at the same time — see Secrets.

Budget for the Platform Timeout

The request timeout is 30 seconds by default and 60 seconds at most — there is no func.toml field that raises it. A request that exceeds it is terminated with a 504. Set your own deadlines on outbound calls a few seconds below the platform’s so you fail with a useful error instead (see Time Out and Retry Outbound Calls), and split work that genuinely needs longer. Exact numbers: Limits.

Name Functions for Their URL

The function name becomes the hostname — {func-name}-{func-id-prefix}.telnyxcompute.com — and new-func registers the function with the platform at scaffold time, so pick the name up front:

Performance

Initialize Once, at Module Scope

A container serves many requests. Module scope runs once per container; the request callback runs per request. Build clients, load config, and compile anything expensive outside the callback:
The same rule holds everywhere: package-level variables in Go, module-level objects in Python.

Keep Cold Starts Small

A new container starts when traffic scales up or after a deploy, and the first request it serves waits for everything before server.listen — imports, client construction, config loads. Keep dependencies minimal, and lazy-load heavy libraries used only on rare paths so the common path doesn’t pay for them.

Cache Expensive Reads in KV

Declare a KV namespace in func.toml and it resolves as a binding on env:
Two contracts to know:
  • expirationTtl is server-side expiry in whole seconds (≥ 1) and requires @telnyx/edge-runtime ≥ 0.2.2 — earlier versions accept the option and silently ignore it.
  • Keys allow a-z A-Z 0-9 - _ / = . and forbid colons — write user/123, not user:123.
More in KV Best Practices.

Put State Where It Belongs

Containers come and go, and concurrent requests can land on different containers — anything kept in process memory is a cache at best. Pick the store by the shape of the data: Don’t build counters, locks, or rate limiters on KV: it has no transactions or compare-and-swap, and concurrent writers to one key are last-write-wins. That job is exactly what Stateful Actors exist for. Full comparison: Where state lives.

Reliability

Make Handlers Idempotent

Clients retry and webhooks are redelivered, so design handlers where processing the same request twice has the same effect as once. Key side effects on a caller-supplied identifier — a webhook event id, an Idempotency-Key header — and skip work already done. If the duplicate check itself must be race-free, do it inside a Stateful Actor; a check-then-act on KV can race.

Keep the Health Endpoint Fast

The scaffolds answer /health before any other routing:
Keep that property: return immediately and never call a dependency from it, so a slow upstream can’t make your function look down.

Catch Everything at the Top of the Handler

Your function is one process. An exception that escapes the request callback — including an unhandled promise rejection — crashes it, drops every in-flight request, and makes the next request pay a cold start. Wrap the whole handler body:

Time Out and Retry Outbound Calls

Don’t let a slow upstream ride you into the platform’s 30-second 504 — set an explicit deadline on every outbound call:
Retry only network failures and 5xx responses, with exponential backoff, and keep the total budget under the platform timeout:

Security

  • Validate input before use — check required fields and types, reject with 400. Nothing between the internet and your handler does it for you.
  • HTTPS only for outbound calls.
  • Never log secret values — and don’t log full request bodies, which may carry PII. Log metadata: method, path, status, duration.
  • Authenticate anything that mutates state — your function URL is public. Require a token or shared secret (stored as a secret, checked in the handler) before acting on a request.

Observability

There is no logs command and no metrics dashboard today. What you have is the output of ship, status, and inspect — plus whatever your function emits itself. So emit deliberately:
  • Send structured events over HTTP to a sink you control (a log aggregator, your own collector) if you need request-level visibility. There is no surface that shows you console.log output.
  • Propagate a request id — read X-Request-ID or generate one, return it in the response, and attach it to every event you emit, so a user-reported failure is findable in your sink.
Patterns and sink examples: Observability.

Next Steps

  • Limits — the exact numbers behind timeouts, memory, and payload sizes
  • Observability — building your own telemetry with no platform logging surface
  • Execution Model — container lifecycle, scaling, and cold starts

Guides

AI Assistants and Edge Compute

Source: https://developers.telnyx.com/docs/edge-compute/guides/ai-assistant-backend.md
Telnyx AI Assistants can call out to your own backend in several scenarios — resolving dynamic variables at the start of a conversation, executing webhook tool calls mid-conversation, and more. Whenever you need a backend for these callbacks, Telnyx Edge Compute is a natural fit: no server to manage, secrets injected at runtime, and deployment via a single CLI command. This guide walks through building a single Go function that handles both dynamic variables and webhook tool calls, using the demo app telnyx-ai-edge as the reference implementation.

What you’ll build

A support assistant for “Telnyx Logistics” that:
  • Greets callers by name (dynamic variables resolved from the caller’s phone number)
  • Has a lookup-order tool the assistant can call to retrieve order status, carrier, and estimated delivery
Both the dynamic variable lookup and the tool call hit one Edge Compute function at a single URL.

Prerequisites


Key concepts

Single function, two callbacks

Edge Compute routes all HTTP methods and paths under your function URL to your handler — path handling is up to your code (see Routes & Domains). The platform handles /health/liveness and /health/readiness probes automatically. In this guide, both the dynamic variables webhook and the webhook tool call point to the same function URL, so the handler dispatches on the request body shape rather than the URL path:
  • Dynamic variables webhook — Telnyx wraps the payload under data.event_type.
  • Webhook tool call — the body is the flat arguments object from the tool’s body_parameters schema (e.g. {"order_id": "ORD-10042"}).
You could also use separate paths (e.g. /dynamic-variables and /tool/lookup-order) if you prefer path-based routing — both approaches work. This guide uses body-shape dispatch to keep everything at a single URL.

Webhook signature verification

Telnyx signs every dynamic-variables webhook and webhook tool call with an Ed25519 key. The signature is in the telnyx-signature-ed25519 header, and the timestamp is in telnyx-timestamp. The signed message is "{timestamp}|{raw_body}". You must verify this signature to confirm the request is genuinely from Telnyx. Your org’s public key is available at:
The response contains data.public (not data.public_key) — the base64-encoded Ed25519 public key.

Dynamic variables response format

The response must nest variables under a dynamic_variables key. A flat object (e.g. {"customer_name": "James"}) is silently ignored — variables will remain unresolved.

Timeout

The default dynamic variables webhook timeout is 1,500 ms. Edge Compute functions may occasionally need more time on a cold start, so consider setting dynamic_variables_webhook_timeout_ms on the assistant to a higher value (up to 10,000 ms). A value of 8,000 ms is a reasonable choice for edge backends.

Step 1: Scaffold the function

This creates a func.toml with the registered function ID and a Go handler scaffold. The Go module must be named function (package function, entrypoint Handle(w, r)). Other module names fail to build: “malformed module path: missing dot in first path element.” Use go 1.24 in go.mod.

Step 2: Store the public key as a secret

Fetch your org’s public key and store it as an encrypted secret. The public key endpoint requires authentication — use your Telnyx API key:
The function reads this secret from os.Getenv("TELNYX_PUBLIC_KEY") at startup. Secrets are never visible in secrets list — only the name is shown.

Step 3: Write the handler

The handler does three things:
  1. Verifies the Telnyx Ed25519 signature on every request
  2. Detects whether the request is a dynamic-variables webhook or a tool call
  3. Returns the appropriate response
handler.go

Step 4: Ship the function

The ship process takes 2–3 minutes. After uploaded successfully, poll telnyx-edge list until the status shows deploy_ok. Don’t trust a CLI timeout as a failure — the function may still be building server-side.
Save the invoke URL — you’ll point the assistant at it next.

Step 5: Configure the AI Assistant

Set the function URL as both the dynamic variables webhook URL and the webhook tool URL on the assistant.

Dynamic variables webhook

In the Portal or via the API: Consider setting the timeout to 8,000 ms to give the function room on cold starts. The default 1,500 ms may be tight for a cold function.

Template variables in the assistant

Use {{variable_name}} in the assistant’s instructions and greeting to reference the variables your function returns:

Webhook tool

Add a webhook tool that points to the same function URL:
When the LLM decides to call lookup-order, Telnyx sends a POST with the tool arguments as the flat body ({"order_id": "ORD-10042"}), signed with the same Ed25519 key. Your function detects the body shape, handles it as a tool call, and returns the result.

Step 6: Test end-to-end

  1. Call the function directly (without a signature — it’ll return 403, confirming it’s live):
  2. Make a test call to the assistant from the Portal or via the API:
  3. Verify in the conversation transcript that:
    • The greeting includes the resolved customer_name
    • The assistant can call lookup-order and read back real order data

Tips and gotchas

Choosing body-shape vs path-based dispatch

Since Edge Compute routes all paths to your handler, you can use path-based routing (e.g. r.URL.Path == "/tool/lookup-order") or body-shape dispatch as shown in this guide. Both work. If you configure separate URLs for the DV webhook and the tool on the assistant, path-based routing is natural. If you point both at the same URL, body-shape dispatch is the way to go. For a path-based routing example, see the RESTful API example in the Edge Compute CLI repo.

Consider a higher webhook timeout

The default dynamic variables webhook timeout is 1,500 ms. Edge Compute functions may occasionally need a bit more time on a cold start, so consider setting dynamic_variables_webhook_timeout_ms to 8,000 ms to give the function room. The maximum is 10,000 ms.

Always verify signatures

Without signature verification, anyone who knows your function URL can inject fake dynamic variables or tool responses. The telnyx-signature-ed25519 and telnyx-timestamp headers are present on every request from Telnyx.

Ship takes a few minutes

A normal ship takes 2–3 minutes. The CLI’s build monitor has a 5-minute timeout, but the build continues server-side regardless. If the CLI reports a timeout, check telnyx-edge list for the actual status before retrying — the function may have deployed successfully.

Secrets require re-shipping

Adding or changing a secret (telnyx-edge secrets add) does not affect an already-deployed function. Run telnyx-edge ship again to pick up the new secret.

The dynamic_variables wrapper is mandatory

Returning a flat JSON object like {"customer_name": "James"} will be silently ignored. Variables must be nested under dynamic_variables:

Next steps

  • Dynamic Variables — full reference for the DV webhook payload and resolution precedence.
  • Webhook signing — how Telnyx signs webhooks and how to verify signatures.
  • Edge Compute quickstart — getting started with your first function.
  • Secrets — encrypted, org-scoped environment variables.
  • Bindings — pre-authenticated Telnyx API client for your function.

Local Development

Local Development

Source: https://developers.telnyx.com/docs/edge-compute/development.md
An Edge Compute function is an ordinary program in a container, not code inside a proprietary runtime. The TypeScript and JavaScript scaffolds run their own HTTP server on $PORT (default 8080); the Python, Go, and Quarkus scaffolds export a handler and the server is run for you. Local development is therefore unremarkable: run the program, curl localhost:8080, iterate, then telnyx-edge ship. Everything below runs against the projects generated by telnyx-edge new-func (see the Quickstart); the Python and Go serve rows add a small local entry point shown in their tabs:

Run and test, by language

The scaffold’s index.ts (or index.js) at the project root is a node:http server. Build (TypeScript only) and run it:
npm run dev runs index.ts directly through ts-node with no build step. The server reads process.env.PORT and falls back to 8080. Exercise it with curl:
The /health fast-path at the top of the scaffold answers the platform’s probes — keep it fast and dependency-free when you rework the server. The scaffold starts its server at module load and exports nothing, so there is nothing to import in a unit test. Keep request handling in exported functions the server delegates to, test those with node --test, and treat curl against the running server as your integration test. The scaffold is an ASGI application: function/func.py exposes a module-level new() factory, and the returned instance’s handle(scope, receive, send) method is the ASGI callable. Any ASGI server runs it — with uvicorn, add a local entry point:
--interface asgi3 is required — handle is a bound method and uvicorn’s interface auto-detection misclassifies it, returning 500s without the flag. --lifespan off silences a harmless Invalid ASGI scope type: lifespan error at startup — the scaffold doesn’t implement the lifespan protocol uvicorn probes for by default. uvicorn also never calls the optional start(cfg)/stop() hooks; only the platform does. For tests, the scaffold’s pyproject.toml already declares pytest, pytest-asyncio, and httpx as dev dependencies, with asyncio_mode = "strict" — mark async tests explicitly. No server is needed: call handle directly with a hand-built scope.
The scaffold exports Handle(w http.ResponseWriter, r *http.Request) from package function — there is no main(); the platform provides the server. net/http/httptest drives Handle without one:
For a curl-able local server, add a scratch main in its own package. The import path is function because that is the module path in the scaffold’s go.mod:
The scaffold is a Quarkus Funqy project. Dev mode gives hot reload on source changes and a debugger on port 5005:
The test stack (quarkus-junit5, rest-assured) is already in the pom.xml:

Testing

A standalone function needs no emulator: the platform runs your program in a container and sends it HTTP, which is exactly what your terminal just did — the language-standard tools above are the whole testing story. (Projects that add Stateful Actors run locally with telnyx-edge dev; see its docs.) The one structural habit worth adopting: keep transport separate from logic. Parse, validate, and compute in plain functions; let the server (or Handle, or handle) stay a thin adapter. That keeps unit tests fast and keeps the binding limitation below out of most of your test suite.

Framework servers

For TypeScript and JavaScript, the platform contract is a process that binds the port in $PORT (default 8080). Any HTTP server that does so runs in the container — Express, Fastify, Hono, or anything else — and the server you ran locally is the server that runs deployed. There are no framework-specific guides.

Bindings and secrets

The env binding surface[telnyx] clients, env.SECRETS.get(), KV namespaces, actors — resolves only inside a deployed function. A standalone function has no local binding emulation — to exercise binding-backed code paths, ship to a scratch function and curl its live URL. (Stateful Actors projects are the exception: telnyx-edge dev runs their actor stack locally so env.<BINDING> calls resolve.) Plain environment variables are the exception. In production, secrets created with telnyx-edge secrets add <key> <value> are injected as environment variables into your functions, and declaring a [telnyx] binding injects TELNYX_API_KEY. Code that reads plain env vars therefore works locally by exporting the same names:
Use throwaway values locally and never commit real ones. See Secrets and Environment variables.

Deploy

When it works locally, ship it:
Each successful ship creates an immutable revision; telnyx-edge revisions list <function> shows them and telnyx-edge rollback <function> <revision-id> retargets traffic to an earlier one. See Deploy.

Next Steps

  • Deploy — ship, revisions, and rollback
  • HTTP handler — the exact entrypoint contract per language
  • Bindings — the env surface a deployed function gets
  • CLI reference — every telnyx-edge command

Configuration

Configuration

Source: https://developers.telnyx.com/docs/edge-compute/configuration.md
Every Edge Compute project has a TOML manifest at its root. It is the one place a function is configured: it identifies what telnyx-edge ship deploys and declares the bindings the runtime resolves onto env. Everything below is a block in that file. There are two forms:
  • func.toml (classic) — a single function. Written by telnyx-edge new-func, which also registers the function server-side, so the UUID func_id is already filled in. Declares [env_vars], [telnyx], [[secrets]], [storage.kv.<NAME>], [storage.cloudstorage.<NAME>], and [storage.sqldb.<NAME>].
  • telnyx.toml (umbrella) — a JavaScript or TypeScript project with a top-level main entry, bundled client-side on ship. Declares the same binding blocks plus [[actors]] and [[ratelimits]], which classic projects cannot.
telnyx-edge types reads either form and writes telnyx-env.d.ts, typing supported env.<binding> declarations. Rate limiter types are not generated in CLI v0.3.0; the rate limiting guide shows the interface to declare. Configuration changes take effect on the next telnyx-edge ship — there is no live update; re-run telnyx-edge types after changing a supported binding declaration so telnyx-env.d.ts matches the manifest. The binding blocks — [telnyx], [[secrets]], [storage.kv.<NAME>], [storage.cloudstorage.<NAME>], [storage.sqldb.<NAME>], [[actors]] (umbrella only), and [[ratelimits]] (umbrella only) — each resolve to a handle on env. This page documents their manifest keys; the bindings catalogue lists every binding at a glance — declaration and env surface — and each block below links to its full documentation.

func.toml

new-func writes the minimal manifest — and because it registers the function server-side at scaffold time, the UUID func_id is already in it:
A manifest using every available block:
There is no language key (the runtime comes from the project files the scaffold creates), no build block, and no timeout key — the request timeout is a platform property (default 30 s, maximum 60 s; see Limits).

[edge_compute] — identity

[env_vars] — environment variables

Free-form key-value pairs injected as process environment variables on each deploy. All values are strings; changes take effect on the next ship; values are plaintext in git — put credentials in secrets instead. Dive in: Environment Variables.

[[secrets]] — secret bindings

The binding is the typed TypeScript surface; independently of it, every secret is injected as an environment variable into all functions in your organization. Dive in: Secrets.

[telnyx] — Telnyx API binding

Declaring the block also injects a TELNYX_API_KEY environment variable into the container — this is how non-TypeScript runtimes call the Telnyx API over plain REST. Documented in Telnyx API binding.

[storage.kv.] — KV namespace binding

Multiple blocks are allowed — each becomes its own env property. telnyx-edge types generates KvNamespace types for these blocks since CLI v0.2.4. Documented in the KV quick start.

[storage.cloudstorage.] — Cloud Storage bucket binding

Multiple blocks are allowed — each becomes its own env property. The runtime injects the credential, so no access key or secret key appears in your code. Documented in Cloud Storage binding.

[storage.sqldb.] — SQL database binding

Multiple blocks are allowed — each becomes its own env property, and two blocks carrying different ids are two separate databases. The id is checked when the function ships: an id that does not exist, belongs to another organization, or has not finished provisioning fails the deploy (currently as a generic HTTP 500 that does not name the binding). Documented in SQL Databases.

telnyx.toml

The umbrella manifest replaces [edge_compute] with top-level keys and adds [[actors]]. On ship, the module graph rooted at main is bundled into a single file with esbuild (TypeScript/JavaScript only) and the manifest ships with it.
The project shape — one module exporting both the actor class and a fetch handler — is covered in Project Structure. telnyx-edge new-func --actor scaffolds it. Documented in Stateful Actors — the configuration reference covers the [[actors]] block in full.

[[ratelimits]] — per-key rate limiting

Multiple blocks are allowed and operate independently. The platform provisions their internal counter storage; there is no rate limiter resource to create first. Documented in Rate limiting.

Configured outside the manifest

The manifest references resources that exist outside it by name or ID — you create each one separately, and the block only points at it:
  • Secret values — stored server-side with telnyx-edge secrets; [[secrets]] and the injected environment variables only reference the key. See Secrets.
  • KV namespaces — created with telnyx-edge storage kv create; [storage.kv.<NAME>] only references the namespace id. See the KV quick start.
  • Cloud Storage buckets — created in the Mission Control portal or over the S3-compatible API; [storage.cloudstorage.<NAME>] only references an existing bucket by bucket_name. See Cloud Storage binding.
  • SQL databases — created with telnyx-edge storage sqldb create; [storage.sqldb.<NAME>] only references the database id. See the SQL Databases quick start.
Rate limiters are the exception: a [[ratelimits]] block creates a platform-managed binding during deployment, so no separate resource or customer KV namespace is required.

Ship-time validation

Binding handles and [env_vars] names share one env namespace. ship (and types) enforce two hard rules and warn on a third:
  • Duplicate [[secrets]] handles are rejectedship fails, because env.SECRETS.get("<handle>") would be ambiguous.
  • A binding (or actor) named SECRETS is rejected when a [[secrets]] block is declared — it conflicts with the env.SECRETS namespace.
  • A name collision between [env_vars] and a binding — including an [env_vars] entry named SECRETS — only warns. Both land on env, so one shadows the other and ship proceeds; rename one.
  • CLI v0.3.0 or later validates each [[ratelimits]] block before upload. name must be non-empty, exact names must not repeat, limit must be positive, and period must be 10 or 60. The deployment service also rejects names that collide after normalization or with another binding section.

Environment Variables

Source: https://developers.telnyx.com/docs/edge-compute/configuration/environment-variables.md
Functions run as real containers, so configuration reaches your code as ordinary process environment variables — process.env, os.environ, os.Getenv, System.getenv. There is no separate configuration API to learn.

What’s in the environment

Declaring variables

Define non-sensitive configuration under [env_vars] in func.toml:
Three behavioral contracts:
  • All values are strings. Parse numbers and booleans in your code.
  • Changes take effect on the next telnyx-edge ship — there is no live update.
  • Names share the env namespace with bindings. If an [env_vars] entry has the same name as a declared binding — or is named SECRETS while a [[secrets]] block is declared — ship warns that one shadows the other on env and still proceeds; rename one. (A binding named SECRETS, or a duplicate [[secrets]] handle, is a hard error.)
[env_vars] values are plaintext in func.toml and end up in version control. Put credentials in secrets instead.

Reading variables

Environment variables vs secrets

Next Steps

  • Secrets — the server-side counterpart for sensitive values, including the typed env.SECRETS surface for TypeScript
  • Bindings — typed, pre-authenticated handles instead of raw variables
  • Configuration — the full manifest reference: every func.toml and telnyx.toml key

Secrets

Source: https://developers.telnyx.com/docs/edge-compute/configuration/secrets.md
Secrets are key-value pairs for sensitive data — API keys, database passwords, signing keys. They are scoped to your organization, stored server-side, and never displayed by the CLI after you set them. Every function receives them; there are two ways to read one.

Managing secrets

The secrets commands take positional arguments:
Secrets are injected into function containers at deploy time — after adding or updating one, telnyx-edge ship each function that uses it.

Reading secrets

As environment variables — every language

Each secret is injected into all functions in your organization as an environment variable named after its key. No declaration needed:

Through the typed binding — TypeScript

TypeScript projects can additionally declare a [[secrets]] binding in func.toml and read the secret through env.SECRETS:
Both surfaces read the same store. The binding adds two things: env.SECRETS.get accepts only the literal union of declared handles — a typo’d handle fails to compile — and the in-code handle is decoupled from the stored key name, so you can swap name in the manifest without touching code. The binding SDK is TypeScript-only today; other runtimes use the injected environment variables. Enforced when [[secrets]] is declared: a binding named SECRETS and duplicate [[secrets]] handles are hard errors — ship fails. An [env_vars] entry named SECRETS only warns (it shadows the env.SECRETS namespace), so rename it. See Bindings for how the env namespace works.

Rotating a secret

add with an existing key overwrites its value:

Scoping and local development

Secrets are organization-scoped. There is no per-environment scoping (dev/staging/prod) today — if you need separation, encode it in the key name (DEV_DATABASE_PASSWORD, PROD_DATABASE_PASSWORD) and pick one in code. There is no local secrets emulation in the CLI. When running a function locally, export the same names as ordinary environment variables:

Troubleshooting

Next Steps

  • Environment variables — the full picture of what lands in your container’s environment
  • Bindings — the declare → typesenv pattern all bindings share
  • Configuration[[secrets]] and every other manifest key

CI/CD

CI/CD

Source: https://developers.telnyx.com/docs/edge-compute/deploy.md
Every Edge Compute deployment from CI is the same three steps: install a pinned telnyx-edge binary, authenticate with auth api-key set, and run ship. This page gives you those steps as working pipelines for GitHub Actions, GitLab CI, and CircleCI, plus the patterns for staging/production and rollback.

The three steps every pipeline runs

Three facts these steps depend on:
  • The CLI ships as GitHub release binaries only — it is not on npm and there is no package manager formula. There is also no un-versioned “latest” asset: releases/latest/download/... URLs return 404. Pin a version in a TELNYX_EDGE_VERSION variable so bumping is a one-line change. For arm64 runners, use the linux-arm64 asset.
  • telnyx-edge does not read a TELNYX_API_KEY environment variable on its own. Store your API key as a CI secret and run telnyx-edge auth api-key set "$TELNYX_API_KEY" as a pipeline step — it persists the key to ~/.telnyx-edge/config.toml for the rest of the job.
  • ship has no environment flag. It deploys the function identified by func.toml in the shipped directory, and its flags are --from-dir and --timeout only. Staging and production are separate functions.
On success, ship prints the function’s live URL (https://{func-name}-{func-id-prefix}.telnyxcompute.com — see Routes & Domains). The URL is stable across deploys.

GitHub Actions

A complete workflow that tests on every push and deploys on pushes to main. Only the install, authenticate, and ship steps are Telnyx-specific — the test job is ordinary npm and assumes a committed lockfile and a test script (the scaffold ships neither); substitute your project’s own checks.
Add the secret under Settings → Secrets and variables → Actions → New repository secret, named TELNYX_API_KEY.

GitLab CI

Define TELNYX_API_KEY as a masked variable under Settings → CI/CD → Variables. The ubuntu:24.04 image runs as root, so no sudo is needed.

CircleCI

Set TELNYX_API_KEY as a project environment variable (Project Settings → Environment Variables) or in a context.

Staging and production

There is no --env flag and no environment promotion — ship always deploys the function that func.toml names. Environments are separate functions, e.g. my-api-staging and my-api, each with its own URL, secrets bindings, and revision history. Register both once, locally (new-func creates the function server-side and writes its UUID func_id into that directory’s func.toml — this is a one-time setup step, not a CI step):
Keep one codebase and both generated func.toml files in the repo; each pipeline job copies the matching one into place before shipping:
Because bindings are declared in func.toml, the two files can also point at per-environment resources — for example a separate KV namespace id per environment.
If you prefer fully separate directories over the func.toml swap, keep one function directory per environment and ship each with telnyx-edge ship --from-dir <path>. If staging and production live in different Telnyx accounts, store one API key secret per account and reference the right one in each job.

Rollback

Every successful ship produces an immutable revision. Rolling back retargets traffic to a previous revision instantly — no rebuild, no re-upload:
Only revisions that reached deploy_ok can be rolled back to. You can wire these commands into a manually triggered pipeline job (e.g. workflow_dispatch on GitHub Actions), but they work just as well from a laptop — rollback does not need your source tree. A git revert + re-ship also works, but it goes through a full build; rollback is the fast path.

Smoke test after deploy

The function URL is stable, and the TypeScript/JavaScript scaffold answers /health with 200 — on other runtimes, point the check at a route your function serves. A post-deploy check is one step:
If it fails, roll back with telnyx-edge rollback as above. There is no platform metrics or logs surface to poll — see Observability for what your function should emit instead.

CI secrets vs. function secrets

Two different things: Function secrets are not deployed from CI variables — manage them with the CLI (the arguments are positional). Values are injected into function containers at deploy time, so re-ship a function after changing a secret it uses. See Secrets.

Troubleshooting

Next Steps


Routes & Domains

Source: https://developers.telnyx.com/docs/edge-compute/configuration/routing.md
Every function deployed with telnyx-edge ship gets a public HTTPS URL. Requests to that URL are the only trigger — there are no cron, queue, or event triggers today. If you need scheduled invocation, point an external scheduler (for example, a GitHub Actions cron job) at the URL.

Public URL pattern

A function named hello-world with func_id 0198c2c5-8f1e-7a3d-9b21-6e4a0d5f1c88 is served at:
telnyx-edge ship prints the URL after a successful deploy:
telnyx-edge list shows the invoke URL for every function in your organization.

Calling your function

All HTTP methods and paths under the function’s URL are routed to your server — path handling is up to your code (see HTTP handler):
Requests time out after 30 seconds by default (60 seconds maximum) — see Limits.

Custom domains

There is no custom domain support today — functions are reachable only at their telnyxcompute.com URL. To serve a function from your own domain, put a proxy you operate (CDN or reverse proxy) in front of it.

Region placement

You can’t pin a function to a region today; the platform chooses placement.

Next Steps


Versions & Rollback

Source: https://developers.telnyx.com/docs/edge-compute/configuration/versions.md
Every successful telnyx-edge ship produces an immutable revision. telnyx-edge revisions list shows a function’s deploy history; telnyx-edge rollback retargets traffic to a previous revision without rebuilding or re-uploading anything.

Listing revisions

Prints the most recent revisions, newest first: the revision ID, when it was shipped, who shipped it, and its deploy status. The revision currently serving traffic is marked with *. Revision IDs are short identifiers like a1b2c3d — you pass one to rollback.

Rolling back

Traffic is instantly retargeted to the existing, immutable revision across all clusters — there is no rebuild and no re-upload. Two constraints:
  • The target must have reached deploy_ok. A revision whose build or deploy failed never served traffic and can’t be rolled back to; revisions list shows each revision’s deploy status.
  • Rollback doesn’t touch your source. Your working tree and git history are unchanged. The next ship deploys whatever is on disk — as a new revision — regardless of which revision is currently active.

Rolling forward

To move forward again, either ship — every successful ship creates a new revision and moves traffic to it — or rollback to any other revision that reached deploy_ok.

Recovering a failed function

Rollback assumes the function has a healthy revision to return to. A function stuck in a terminal failure state (build_failed, deploy_failed, delete_failed) can instead be reset:
This tears down the function’s deployed resources and returns it to the created state — preserving its ID, name, and config — so you can fix the code and ship again. A healthy function (build_ok/deploy_ok) can’t be reset; use delete-func if you want it gone.

Next Steps

  • CI/CD — ship from a pipeline; rollback is your escape hatch
  • Routes & Domains — the function URL always points at the active revision
  • CLI referenceship, revisions, rollback, and reset-func in full

Runtime APIs

Overview

Source: https://developers.telnyx.com/docs/edge-compute/runtime.md
Most serverless platforms hand you a sandbox: a restricted runtime, a fixed set of provided APIs, one supported way to return a response. Edge Compute doesn’t. A function is a real Linux container running your language’s own runtime — so the bulk of your “runtime API” is just the standard library and any dependency you install, exactly as it behaves on any Linux box. What the platform adds on top is small and explicit, and this section documents it end to end:
  • An execution environment — how containers start, stay warm, scale, and get a request budget. This is the architecture your code runs inside.
  • An entrypoint contract — which file the platform runs and how a request reaches your code, per language.
  • Bindings — declared connections to platform resources: the Telnyx API, secrets, KV, and object storage, with credentials injected for you so nothing sensitive lives in your code.
Everything else — HTTP parsing, crypto, file I/O, database drivers — comes from your language, not from the platform.

Real containers

Because a function is a real container:
  • Native runtimes — Node.js, Go, Python, and Java (Quarkus) run as themselves. No fetch-only sandbox, no restricted language subset.
  • Any dependency that installs — npm packages, Go modules, PyPI packages, Maven artifacts.
  • POSIX environment — environment variables, plus file I/O in the working directory and /tmp. The root filesystem is read-only and writes are ephemeral — they don’t survive the container being recycled, so persist real data in KV or a bucket.
  • Outbound network — HTTP clients, TCP sockets, DNS resolution.
The trade-off is container lifecycle: instances cold-start, stay warm between requests, and are recycled. Execution model covers what that means for initialization and in-memory state.

The entrypoint contract

HTTP is the only trigger. What “handling a request” means differs by language: The exact per-language contract — files, signatures, health probes, bodies, the request budget — is specified in HTTP handler.

Bindings

A function reaches platform resources — the Telnyx API, secrets, KV, and object storage — through bindings you declare in the project manifest. The platform injects the credential, so no keys or tokens appear in your code. How you reach a binding depends on the language:
  • TypeScript gets a typed handle for each declared binding, resolved at runtime.
  • Go, Python, and Java reach the same resources through injected environment variables (the Telnyx API key, each secret) and REST.
Bindings documents every binding type and how to declare it; Environment variables and Secrets cover configuration.

In this section

How your code runs: cold starts, warm reuse, scaling, the request budget, and where state lives. The per-language entrypoint contract — files, signatures, health probes. Reach platform resources — Telnyx API, secrets, KV, object storage — from any language.

Execution Model

Source: https://developers.telnyx.com/docs/edge-compute/runtime/execution-model.md
An Edge Compute function is a Linux container running an HTTP server — one you run yourself in TypeScript and JavaScript, one run for you in Go, Python, and Java. The platform starts containers when traffic arrives, reuses them while it continues, and reclaims them — down to zero — when it stops. Everything on this page follows from that.

Request path

  1. Route — a request to https://<func-name>-<func-id-prefix>.telnyxcompute.com reaches the platform (routing).
  2. Place — a warm container takes it, or a new one starts (a cold start).
  3. Execute — the server process handles the request and writes the response.
  4. Keep warm — the container stays up for subsequent requests until it is recycled.

Container lifecycle

Cold start

A cold start is the first request’s cost of a new container: the image starts, the language runtime boots, your module-level code runs, and then the request is served. Put expensive setup — HTTP clients, connection pools, parsed config — at module scope so it runs once per container instead of once per request:
Curl that function twice: a near-zero containerAgeMs means the request paid a cold start; a growing one means the container was reused. The same split exists in every runtime — package-level vars and init() in Go, module scope or the optional start(cfg) hook in Python, application-scoped state in Quarkus. The per-language entrypoint contracts are in HTTP handler.

Warm reuse

While traffic continues, requests land on existing containers and skip initialization. Module state persists between requests on the same container — treat it as a cache keyed by container, nothing more. Two requests may or may not share a container, and the platform gives you no way to control which.

Recycling and scale to zero

Containers are reclaimed without notice: after idling, when a new revision is shipped (telnyx-edge ship — see Versions), or by platform scaling decisions. At zero traffic a function scales to zero containers; the next request pays a cold start. Treat container memory like a process that can be killed at any instant: only what you wrote to durable storage is real. See Where state lives for what “durable storage” means here. In Python, your function class may define an optional stop() hook, called on scale-down or update — use it for best-effort cleanup, never for durability.

Scaling

The platform scales the container count with concurrent load. There is no concurrency knob to configure — scaling is automatic.

Request timeout

A function must respond within 30 seconds by default, 60 seconds maximum; a request that exceeds the budget is terminated with a 504. There is no func.toml field for this — see Limits for the full table. Budget outbound calls below the deadline so you return a real error instead of being cut off:

Triggers

HTTP is the only trigger — there are no cron, queue, or event triggers. A Telnyx webhook (a messaging profile or Call Control application pointed at your function URL) is just an HTTP request, so your function handles it like any other — see Receiving messages and Handling calls. For periodic work, call the URL from an external scheduler (a GitHub Actions cron job is enough), or use a Stateful Actor alarm to fire a callback on the platform itself.

Where state lives

Module state dies with the container, so anything that must survive needs a home: Don’t build counters or per-entity coordination on KV — concurrent read-modify-write races there, which is exactly the problem Stateful Actors exist to solve.

Next Steps

  • HTTP handler — the entrypoint contract per language
  • Limits — timeouts, memory, and payload caps
  • Versions — revisions, ship, and rollback

HTTP Handler

Source: https://developers.telnyx.com/docs/edge-compute/runtime/http-handler.md
HTTP is the only way a function is invoked. Requests arrive at https://<func-name>-<func-id-prefix>.telnyxcompute.com (see Routing) and are handed to your entrypoint — but what “your entrypoint” means differs by language. In TypeScript and JavaScript you own and run the HTTP server (the CLI scaffolds a working one); in Go, Python, and Java the server is run for you and your code is called per request. telnyx-edge new-func -l <language> generates a working entrypoint for each language. The code on this page is that scaffold — start from it rather than a blank file.

The contract at a glance

The scaffold, by language

Each tab is one language’s entrypoint contract and the scaffold new-func generates for it. You own the server. There is no framework-provided handler(request) entrypoint and no Response object to return — your function is a container running a plain node:http server (or any server framework you install). Two things are contractual:
  • Listen on process.env.PORT, falling back to 8080.
  • Answer /health (and paths under it) with a 200. The platform’s liveness and readiness probes hit it — a function that doesn’t answer isn’t routed traffic and can be restarted. Keep the probe path fast: respond before any other work.
index.ts lives at the project root, next to func.toml — not in src/. The scaffold, lightly condensed (comments trimmed):
The JavaScript scaffold (-l js) is the same file minus type annotations, at index.js (same project-root location). Request bodies arrive as data events carrying Buffer chunks. The scaffold accumulates them as a string, which is fine for text and JSON — for binary bodies collect the buffers instead (chunks.push(chunk) then Buffer.concat(chunks)), because toString() corrupts non-UTF-8 bytes. The platform owns the server. You write an ordinary net/http handler — exported as Handle, in package function, with no main(). The platform binds the port and routes requests to Handle; the scaffold defines no health route and doesn’t need one. handler.go — the scaffold, lightly condensed (comments trimmed):
go.mod declares module function (Go 1.24). Standard net/http semantics apply: read the body from r.Body, set headers with w.Header().Set(...) before the first write. The contract is ASGI. Your project is a function/ package whose func.py exposes a module-level new() factory; the runtime calls it when an instance starts, keeps the returned object, and dispatches every HTTP request — except liveness and readiness probes, which the platform answers itself — to its handle coroutine. There is no handler(request) returning a dict, and no requirements.txt — dependencies go in pyproject.toml (the scaffold uses hatchling). function/func.py — the scaffold, trimmed (scope validation and logging removed):
function/__init__.py re-exports the factory: from .func import new. Functions are Quarkus Funqy functions: a plain class with a method annotated @Funq that takes a bean and returns a bean. Quarkus owns the server, deserializes the JSON request body into your input bean, and serializes your return value back to JSON. src/main/java/functions/Function.java — the scaffold:
Input and Output are plain beans in the same package — a message field with a no-arg constructor, getter, and setter. Invocation is JSON in, JSON out:
Two scaffold defaults worth knowing:
  • application.properties selects the exported method by name: quarkus.funqy.export=function. Rename the method, update the property.
  • Health endpoints come from SmallRye Health, pre-configured at /health/liveness and /health/readiness — leave them in place.
Funqy is a typed JSON model: your method sees the deserialized bean, not raw bytes, URL paths, or headers. For raw HTTP semantics — routing on paths, custom headers, binary bodies — use the TypeScript, JavaScript, Go, or Python contract instead.

Bodies, headers, and binary data

These apply to the raw-HTTP contracts — TypeScript, JavaScript, Go, and Python. Java/Funqy is the exception: it’s typed JSON in/out, so raw bodies, binary responses, and custom headers aren’t available from a @Funq method (see the Java tab).
  • Bodies pass through raw, both directions. There is no base64 envelope and no JSON wrapping between the caller and your code. To serve binary, set the Content-Type and write the bytes:
  • Headers are yours. Whatever your server (or Handle, or http.response.start) sets is what the caller receives. There is no platform header rewriting to work around.
  • Bodies are size-capped. Request and response body limits are listed in Limits.

The request budget

A function has 30 seconds by default to respond, and 60 seconds at most. Past the budget the request is terminated and the caller gets a 504. This is a platform limit, not a func.toml field — there is no timeout_seconds setting. For work that can run long, set your own internal timeout a few seconds under the platform’s and return an error or partial result instead of being cut off mid-response. Exact numbers and the other caps — memory, body size, deploy rate — are in Limits.
  • Bindings — the typed env surface: Telnyx API, secrets, KV
  • Execution model — lifecycle, cold starts, concurrency
  • Limits — timeouts, body size, memory

Overview

Source: https://developers.telnyx.com/docs/edge-compute/runtime/bindings.md
A binding maps a name you declare in the project manifest to an authenticated resource handle, resolved by the runtime — the credential is injected for you and never appears in your code, bundle, or logs. Each binding resolves on the env object (from @telnyx/edge-runtime) — env.MY_TELNYX, env.SECRETS, and so on. The env object and telnyx-edge types are TypeScript-only today. Other runtimes (js, go, python, quarkus) don’t get the typed env handle, but reach the same resources through the credentials injected into the container — see Bindings from other languages.

Every binding works the same way

The binding name (MY_TELNYX) is yours to choose; it becomes the property on env. telnyx-edge types writes telnyx-env.d.ts from the manifest — re-run it after every binding change. Typing for [storage.kv.<name>] blocks requires CLI v0.2.3 or later. The SDK types .data as T | undefined for list calls. Under tsc --strict, indexing into data (e.g. data.length) fails with TS18048: 'data' is possibly 'undefined'. Coalesce before use: const arr = list.data ?? [];.

Catalogue

Manifest: func.toml or telnyx.toml

Bindings are declared in your project manifest. telnyx-edge types reads either form and types supported env.<binding> declarations; CLI v0.3.0 does not yet generate a rate limiter declaration.
  • func.toml (classic) — the standard [edge_compute] project file. Can declare [telnyx], [[secrets]], [storage.kv.<name>], [storage.cloudstorage.<name>], and [storage.sqldb.<name>].
  • telnyx.toml (umbrella) — a manifest with top-level name and main. Declares the same bindings, plus [[actors]] and [[ratelimits]]. Actor classes are imported from main; rate limiters are passed to the bundled handler on env.

Bindings from other languages

The env SDK surface is TypeScript-only, but the credentials behind it are not:
  • Telnyx API — declaring [telnyx] also injects a TELNYX_API_KEY environment variable into the container at runtime. Any language can call the Telnyx REST API with it as a bearer token — see Using the Telnyx API.
  • Secrets — every secret is also injected as a plain environment variable into all your functions (os.environ["DEMO_GREETING"], os.Getenv("DEMO_GREETING"), …). env.SECRETS.get() and the environment variable are two views of the same value.
  • KV — any language can use the KV REST API with the injected TELNYX_API_KEY.
  • Object storage — the typed env binding is TypeScript only; from any language, reach the same buckets over the S3-compatible API with your own access keys.
  • SQL Databases — the typed env binding is TypeScript only; from any language, query the same database over POST /v2/storage/sqldbs/{id}/actions/query with a Telnyx API key as a bearer token. TELNYX_API_KEY is only in the environment when the function also declares [telnyx], so declare that block as well or supply the key as a secret. That endpoint takes no bound parameters, so use it for SQL you wrote yourself, never for values that came from a caller.
  • Stateful Actors — TypeScript only; there is no REST fallback today.
  • Rate limiting — bundled JavaScript/TypeScript functions only; there is no REST fallback or environment-variable equivalent. telnyx-edge types v0.3.0 does not generate this binding’s type, so declare its { limit({ key }): Promise<{ success: boolean }> } interface in your project.

Bindings vs secrets

  • Binding — a Telnyx or platform resource, authenticated for you (env.MY_TELNYX).
  • Secret — a value you supply (env.SECRETS.get("STRIPE_KEY")).
Use a binding for platform resources; use a secret for your own third-party credentials.

Next Steps


Overview

Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api.md
The Telnyx API binding puts a ready-to-use, authenticated Telnyx client on env. You never handle an API key — the binding injects credentials at the edge and keeps them out of your code, bundle, and logs.
  • Free-formed nameMY_TELNYX is whatever you set as binding in func.toml. It becomes the property on env.
  • Typedtelnyx-edge types types env.MY_TELNYX as the Telnyx client.
  • One per organization — every function in the org shares it.
Start with the Quick start. The org-level credential behind the binding (bindings create / validate / update) is account-level and rarely touched — see the CLI reference.

Quick Start

Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/quick-start.md
A complete function that returns your Telnyx account balance — declared, typed, shipped, and called.

1. Create a function

2. Declare the binding

Add a [telnyx] block to the generated func.toml:

3. Generate types

env.MY_TELNYX is now typed as the Telnyx client.

4. Write the handler

5. Ship

6. Call it

ship prints your function URL. Hit it:

API Reference

Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/api-reference.md
env.MY_TELNYX is a ready-to-use, authenticated Telnyx client handle — call it like any Telnyx API client, with auth already wired in (no new Telnyx(...), no API key to manage). Calls take the shape env.MY_TELNYX.<resource>.<method>(...), using resource and method names — not raw HTTP paths:
  • <resource> — a camelCase property: messages, calls, balance, availablePhoneNumbers, …
  • <method> — a method on the resource: .send, .dial, .list, .retrieve, …
The names don’t always track the HTTP API (messages.send is POST /messages, but messages.cancelScheduled is DELETE /messages/{id}), so discover them rather than guessing from endpoints:
Each method returns the API response; list and retrieve calls expose the payload on .data.

Errors

Calls reject on API errors. Catch and inspect:

Receiving Messages

Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/receiving-messages.md
Inbound SMS is webhook-driven: Telnyx POSTs a message.received event to your messaging profile’s webhook, and your Edge Compute function is that webhook. Replying to another Telnyx number is on-net — no 10DLC campaign required.

1. Write the handler

Declare the binding in func.toml (see the Quick start):

2. Ship

3. Point a messaging profile at it

Set a messaging profile’s inbound webhook to your function URL, then assign your number to that profile:

4. Test on-net

Send from another Telnyx number on your account to your function’s number:
You get back “You said: hello” on-net, and GET https://YOUR-FUNC.telnyxcompute.com shows what arrived. The inbound event the function parses looks like:
Only on-net replies skip 10DLC. Receiving is always free. Replying to a Telnyx number is on-net (no campaign). Replying to an off-net number — e.g. a personal mobile — is application-to-person traffic and requires 10DLC registration.

Keyword auto-reply

The handler above echoes every message. To answer commands instead, replace the messages.send call in step 1 with a keyword match:
This handles the STOP message itself but doesn’t remember it — store opted-out numbers in KV and check before every send.

Handling Calls

Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/handling-calls.md
Inbound voice is webhook-driven through Call Control: Telnyx POSTs call events to your Call Control application’s webhook, and your Edge Compute function is that webhook. On call.initiated you answer the call; on call.answered you play audio.

1. Write the handler

Declare the binding in func.toml:

2. Ship

3. Point a Call Control app at it

4. Test

Call the number from any phone. The function answers and plays your audio. audio_url must be a publicly reachable HTTPS .mp3 or .wav. The flow is two events — call.initiated (answer) then call.answered (play) — so handle both. To loop, hang up, or chain more actions, respond to later events (call.playback.ended, call.hangup) the same way.

Time-of-day routing

To route callers to a person instead of playing audio, replace both event branches in step 1 with a single transfer on call.initiated — Telnyx dials the destination and bridges the caller when it answers, so there’s nothing to do on call.answered:
If the transfer fails, you get a call.hangup webhook for the destination leg and the caller’s leg stays active — transfer to an alternate number or answer and play a message.

Overview

Source: https://developers.telnyx.com/docs/edge-compute/rate-limiting.md
A rate limiter binding caps how many requests a key can make during a fixed window. Your function chooses the key—for example, an authenticated user ID, tenant ID, or hashed API-key identifier—and decides how to respond when the budget is exhausted. Rate limiters are available to bundled JavaScript and TypeScript functions that use telnyx.toml. Each binding is exposed on the handler’s env argument as env.<NAME>. You don’t create a KV namespace or manage counter storage. Start with the Quick Start to add a rate limiter to a function and test it in production.

Configuration reference

The period is an integer, not a duration string: use period = 60, not period = "60s". Configuration changes take effect after the next telnyx-edge ship.

Multiple rate limiters

Each [[ratelimits]] block has an independent counter namespace. This lets one function apply different budgets to different plans or operations:
Derive both userId and tier from authenticated, trusted application state. A caller-controlled header or query parameter lets a client choose a fresh key and bypass its intended budget.

Choose keys carefully

Avoid putting secrets or other sensitive values directly in a key.

Behavior and limitations

  • Quick Start — Configure, deploy, and test a rate limiter
  • API reference — Runtime method inputs, return values, and failure behavior
  • Bindings — How runtime resource handles are declared and resolved
  • Configuration — The complete telnyx.toml reference
  • Deploy a function — Ship configuration and code changes

Quick Start

Source: https://developers.telnyx.com/docs/edge-compute/rate-limiting/quick-start.md
This walkthrough configures a function to allow two requests per authenticated user during each 10-second window. Use telnyx-edge v0.3.0 or later. Earlier CLI versions may upload an invalid [[ratelimits]] block instead of rejecting it locally.

1. Declare a rate limiter

Add a [[ratelimits]] block to telnyx.toml:
This configuration allows two successful checks per key in each 10-second window.

2. Check the limit in your handler

The binding returns a decision; it does not send a 429 response automatically. telnyx-edge types v0.3.0 does not yet generate rate limiter declarations. Define the small RateLimiter interface in your project, as shown above.

3. Ship and test

For the configuration above, three requests with the same trusted user ID during one window produce two 200 responses followed by a 429:
The CLI validates each declared limiter before upload and prints its name, limit, and period. See the API Reference for the binding’s method contract and return values.

API Reference

Source: https://developers.telnyx.com/docs/edge-compute/rate-limiting/api-reference.md
Each [[ratelimits]] entry in telnyx.toml exposes a rate limiter binding on the handler’s env argument. The binding name is uppercased and hyphens are replaced with underscores, so a limiter named api-limit is available as env.API_LIMIT.

env.NAME.limit({ key })

Checks and increments the counter for key in the current fixed window.

Input

Return value

The method returns Promise<{ success: boolean }>: Rate limiting fails closed: treat success: false as a rejection. The binding returns only the decision. Your function is responsible for returning an appropriate response, such as HTTP 429 Too Many Requests.

TypeScript type

telnyx-edge types v0.3.0 does not yet generate rate limiter declarations. Define the binding interface in your project:
See Rate Limiting for configuration, usage patterns, and platform behavior.

Observability

Observability

Source: https://developers.telnyx.com/docs/edge-compute/observability.md
Edge Compute has no customer-facing telemetry surface today: there is no telnyx-edge logs command, no log dashboard, and no metrics or traces. console.log output from a running function is not readable anywhere. What you can observe is built from three things — the CLI’s control-plane views, your function’s own health endpoint, and structured events your function emits over HTTPS to a collector you run.

Control-plane visibility

The CLI answers “is it deployed, and where does it answer” — not “what is it doing”: Add -v to any command for verbose client-side logging when a command itself misbehaves. None of this shows requests, errors, or output from the running container. For that, read on.

Health checks

The scaffolded TypeScript and JavaScript entrypoints answer /health before any other routing:
Keep this route dependency-free — no KV reads, no outbound calls — so an external checker can tell “function down” apart from “dependency down”. The Quarkus scaffold serves /health through SmallRye Health; in Go and Python, add an equivalent route yourself. HTTP is the only trigger, so probing is external by design: point an uptime monitor — or a scheduled job such as a GitHub Actions cron — at https://<func-name>-<org>.telnyxcompute.com/health.

Emit events to a sink you run

Since nothing shows you a running function’s output, the pattern is to send structured events over HTTPS to a collector you control — any log store with an HTTP ingest endpoint works. Store the collector’s credential as a secret, never in code. Declare the secret in func.toml and store its value:
Then instrument the entrypoint. This emits one event per request — off the critical path, with a deadline, and never able to fail the response:
What makes this pattern hold up:
  • Propagate a request id. Read X-Request-ID or generate one, return it in the response, and attach it to every event — a user-reported failure becomes findable in your sink.
  • Log metadata, not payloads. Method, path, status, duration. Never secret values, and not full request bodies, which may carry PII.
  • Buffering trades loss for volume. The per-request emit above is the simple, safe default. If volume demands batching, remember events buffered in memory are gone when the container stops — see Execution model for the container lifecycle.
The env.SECRETS binding is TypeScript-only, but secrets are also injected as plain environment variables into every function, so the same pattern works in any runtime: read the key from the environment (os.Getenv("LOG_SINK_KEY") in Go, os.environ in Python) and POST JSON to your sink.

Next Steps

  • Best Practices — error handling and outbound-call deadlines the emitter should respect
  • Limits — the 30 s default / 60 s max request budget your telemetry lives inside
  • Secrets — both access surfaces for the sink credential
  • CLI Reference — full flags for list, inspect, status, and revisions

CLI

CLI Reference

Source: https://developers.telnyx.com/docs/edge-compute/reference/cli.md
telnyx-edge is the command-line tool for Edge Compute: it scaffolds function projects, deploys them, and manages the resources they bind. This page covers every command in v0.2.5.

Installation

The CLI ships as GitHub release binaries only — it is not on npm and there is no Homebrew formula. Assets are version-stamped; there is no un-versioned “latest” asset (releases/latest/download/... URLs return 404). Each tarball extracts into a versioned directory containing the telnyx-edge binary:
For macOS, substitute macos-arm64 (Apple silicon) or macos-amd64 (Intel) in both lines. To update, download the new version’s asset and replace the binary the same way.

Global flags and configuration

Credentials persist in ~/.telnyx-edge/config.toml. Two environment variables affect the binary itself: TELNYX_CONFIG_PATH relocates the config file, and TELNYX_NO_UPDATE_CHECK disables the release update check.

auth

login opens a browser for OAuth; api-key set writes the key to ~/.telnyx-edge/config.toml. Both end in the same place — subsequent commands read the stored credential. The CLI does not read a TELNYX_API_KEY environment variable. In CI, run telnyx-edge auth api-key set "$TELNYX_API_KEY" as a pipeline step — see CI/CD.

new-func

new-func does two things: it creates a project directory (the command fails if one with that name already exists), and it registers the function server-side — so it requires authentication, and the generated func.toml already contains the function’s UUID func_id. Rapid successive calls can hit HTTP 429 rate limits. What each scaffold contains: The entrypoint contract differs per language — see HTTP handler.

ship

ship uploads, builds, pushes, and deploys the function named by the directory’s func.toml. There is no environment flag — staging and production are separate functions. Umbrella projects (telnyx.toml) are bundled client-side before upload: the module graph rooted at main is compiled into a single file with esbuild (TypeScript/JavaScript only), and the manifest is included so the platform can deploy any [[actors]] it declares. On success, ship prints the live URL — stable across deploys:
The scheme is {func-name}-{func-id-prefix}.telnyxcompute.com — see Routes & Domains. Each successful ship also produces an immutable revision (revisions, rollback).

list

Lists your functions — id, name, status, creation time, and invoke URL. Paginated: --page (default 1) and --page-size (default 25).

inspect

Shows one function’s status, invoke URL, and timestamps, plus the actor types it binds — each binding’s type, status, and owner/reference role.

status

Self-diagnostics: config file existence, authentication status, and connectivity to https://api.telnyx.com. Run it first when any other command misbehaves.

revisions

Lists the most recent revisions for a function, newest first, with each revision’s id, ship time, author, and deploy status; the revision currently serving traffic is marked. Every successful ship produces an immutable revision — see Versions & Rollback.

rollback

Instantly retargets traffic to an existing, immutable revision across all clusters — no rebuild, no re-upload. Only revisions that reached deploy_ok can be rolled back to; get ids from revisions list. Your source tree is untouched — the next ship deploys whatever is on disk, as a new revision.

secrets

Secrets are organization-scoped key-value pairs for sensitive data. The arguments are positional — there are no --name/--value flags:
add on an existing key overwrites it. Values are injected at deploy time, so ship each function that uses a changed secret. Functions read secrets two ways, and both are always true: every secret is injected as a plain environment variable into all functions in your organization, and TypeScript functions can additionally declare a [[secrets]] binding and read through the typed env.SECRETS.get(). See Secrets for both surfaces.

bindings

Manages the org-level Telnyx credential (one per organization) behind the Telnyx API binding. The per-function flow needs none of these commands — declaring [telnyx] in func.toml wires the binding automatically on ship.

types

Generates TypeScript types for the env surface from your manifest (func.toml or telnyx.toml), folding every declared binding into one global Env interface: Declarations only — no JavaScript, no runtime glue, no source edits. Re-run after changing any binding declaration. types generates a .d.ts consumed by tsc — it has no effect on js, go, python, or quarkus runtimes. Bindings on those runtimes are reached over REST instead; see Bindings.

storage

Manages KV storage namespaces and keys: storage kv covers namespace create/list/get/delete, and storage kv key covers put/get/list/delete including server-side TTL and prefix listing. Full flags and examples live in the KV CLI reference. storage sqldb manages SQL databases: create/list/get/delete, plus execute for running SQL against a database out-of-band and migrations for versioned schema files. It arrives in a later release than the v0.2.5 covered above — see the SQL Databases CLI reference for the version floor, full flags, and examples.

actors

Inspects and manages the Stateful Actor types registered to your account (account-scoped, keyed by type). inspect reports the actor type’s live instance count; instances lists the persisted instances (type/id pairs, e.g. Counter/alice). Output renders backend state — never inferred from local files.

reset-func

Tears down a failed function’s deployed resources and returns it to the created state — preserving its id, name, and config — so you can fix the code and ship again. Allowed only from a terminal failure state (build_failed, deploy_failed, delete_failed); a healthy function can’t be reset (use delete-func), and an in-progress operation must finish first.

delete-func

Deletes a function by name. This cannot be undone — the function, its revisions, and its URL are gone.
  • Configuration — every func.toml / telnyx.toml key the CLI reads
  • CI/CD — install, authenticate, and ship from a pipeline
  • Versions & Rollback — how revisions and rollback behave
  • KV CLI — the full storage kv surface
  • SQL Databases CLI — the full storage sqldb surface, including execute and migrations
  • Stateful Actors — the projects behind --actor and the actors command

Platform

Pricing

Source: https://developers.telnyx.com/docs/edge-compute/platform/pricing.md
Functions bill on two meters: requests (each HTTP request your function handles) and CPU time (metered in milliseconds). These are the only two meters — there is no charge for deploying, for the number of functions you keep, or for idle functions.

Functions

Storage

Storage is billed separately from function execution:
  • KV bills per operation and per GB-month stored — see KV Pricing.
  • Limits — execution, size, and rate limits
  • KV Pricing — operation and storage rates for KV

Limits

Source: https://developers.telnyx.com/docs/edge-compute/platform/limits.md
Limits are behavioral contracts: each one states what the platform enforces, what you see when you hit it, and what to do instead.

Execution Limits

Request timeout. A function must respond within the timeout or the request is terminated with a 504 Gateway Timeout. The timeout is not set in func.toml — there is no timeout_seconds field. Budget your own upstream calls below the platform limit (for example, a 25-second timeout on outbound requests) so you can return a real error instead of a 504. Memory. Each container has a fixed allocation. Exceed it and the container is terminated; the next request starts a fresh one (a cold start). Stream large payloads instead of buffering them, and don’t let in-memory caches grow unbounded — memory is per-container and disappears on restart anyway. For durable state, see Where state lives.

Function Limits

The code size limit includes dependencies after compression. If you hit it: remove unused dependencies, exclude development dependencies from the shipped directory, or split into multiple functions.

Network Limits

The outbound connection count covers HTTP/HTTPS requests, database connections, and TCP sockets. Since a container serves many requests over its lifetime, open clients and connection pools once at module scope and reuse them across requests rather than reconnecting per invocation.

Rate Limits

Function creation is also rate limited: telnyx-edge new-func registers the function server-side at scaffold time, and rapid successive calls return 429. Wait and retry. There is no hard cap on concurrent invocations — the platform scales containers with traffic. Each new container pays a cold start, so sharply spiky traffic sees higher tail latency.

Account Limits

Total requests and CPU time are usage-billed with a monthly free tier — see Pricing. Need higher limits? Contact support@telnyx.com.

KV Storage Limits

KV Best Practices is the authoritative KV limits page. For values over 1 MiB, store the object in Cloud Storage and keep a reference in KV.

When a Limit Is Exceeded