Self-hosting (BYOC)
Self-hosting (BYOC)
Self-hosting lets you export a saved flow or agent as a self-contained
@runtypelabs/runtime definition and run it on your own infrastructure
(Cloudflare Workers, Cloud Run, Node, Vercel Functions, anywhere a runtime
adapter is supplied). The exported runtime still reaches back to Runtype for the
platform capabilities it needs — model execution through the platform-key proxy,
plus hosted memory, crawl, email, search, and asset storage.
Self-hosting (BYOC, bring your own cloud) is an Enterprise-plan capability. The export endpoints
and every hosted /v1/runtime/* endpoint below are documented for everyone,
but calls from an account that is not on an Enterprise plan return HTTP 403
with the machine code BYOC_PLAN_REQUIRED. Contact support
to enable self-hosting for your account.
Exporting a flow or agent
The export endpoints are in limited preview: they are dark-launched and return
404 Not Found in production until general availability. They are reachable
in non-production environments today.
Export a saved flow or agent version as a runtime definition:
The response is a self-contained runtime definition you vendor into your own deployment. On an ineligible plan both endpoints return:
Hosted capability endpoints
A standalone runtime authenticates to these endpoints with an ordinary Runtype API key (Bearer token) and reaches the same platform capabilities its flow/agent steps use inside Runtype. Tenant identity is always taken from the authenticated key — identity fields in the request body are ignored.
Each requires an Enterprise plan and returns the same 403
(BYOC_PLAN_REQUIRED) for ineligible accounts. See the
API Reference for the full request and response schema of each
endpoint.
Where a key is permission-scoped, these endpoints expect an execute scope
(FLOWS:EXECUTE or AGENTS:EXECUTE); POST /v1/runtime/assets expects
ASSETS:WRITE.
Three capabilities have no hosted endpoint and must be supplied by the host instead: Firecrawl and Massive fetch methods, embedding models beyond the built-ins, and collection schema validation.
Reporting telemetry
POST /v1/telemetry/ingest is separate from the table above. It is not
Enterprise-gated, and it is what RuntimeConfig.telemetry posts to so a deployed
runtime’s executions appear in the Runtype dashboard.
Give it a key scoped to TELEMETRY:WRITE, minted from the “Telemetry Ingest”
preset in the API-key dialog. Ingest is append-only, and this is the credential a
deployment tends to copy the furthest, so it should grant push and nothing else.
Make it a separate credential from the one your runtime uses for the hosted endpoints above. A single key serving both still needs the execute scopes those endpoints require, which defeats the point of scoping the telemetry key at all. Two keys cost nothing and the telemetry one can then be handled far more casually than the other.
The endpoint still accepts any valid API key, so an existing deployment keeps reporting after an upgrade. Keys without the scope are recorded server-side so we can tell when the allowance is no longer needed.
If what you want to report on is an agent that runs entirely outside Runtype rather than an exported one, see Reporting telemetry from an external agent, which covers the OpenTelemetry path.
The public POST /v1/assets first-class asset endpoint is not part of
self-hosting and is available on every plan — only its runtime-family
mount (POST /v1/runtime/assets) is Enterprise-gated.
Firecrawl and Massive fetch methods
fetch-url steps using standard HTTP run through the runtime adapter. Firecrawl
and Massive are managed-provider methods: a self-hosted runtime must supply a
ManagedFetchExecutor through RuntimeConfig.managedFetchExecutor to resolve
credentials, enforce provider policy, and perform those requests.
There is no hosted /v1/runtime/* endpoint for managed fetch. If the seam is
omitted, the default context-step executor returns an actionable unsupported
error instead of attempting the provider call. If you supply a custom
contextStepExecutor, that executor owns the managed-fetch integration itself.
Embedding models beyond the built-ins
generate-embedding steps, and the query embedding inside vector-search,
embed natively for the OpenAI and Google model families
(text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002,
text-embedding-004, gemini-embedding-001). Those run through the runtime
adapter and need nothing beyond the provider key your adapter resolves
(OPENAI_API_KEY / GOOGLE_API_KEY).
Any other embedding model id is a host capability: supply an
EmbeddingProvider through RuntimeConfig.embeddingProvider. Inside Runtype
this is how the Weaviate-vectorizer models (text2vec-openai,
text2vec-cohere, text2vec-huggingface, and the
Snowflake/snowflake-arctic-embed-* family) are served — their vectors are
produced by the customer’s own Weaviate instance, so the credentials and
configuration cannot travel with an export.
There is no hosted /v1/runtime/* endpoint for embedding. If the seam is
omitted, those steps fail with an actionable
EmbeddingProviderNotConfiguredError naming the model, rather than falling back
to a different model. Built-in models keep first refusal, so wiring a provider
never changes how an OpenAI or Google model is embedded. For tests and offline
runs the runtime also exports StaticEmbeddingProvider, which is not a real
embedder — its vector is a hash of the input text.
Check your exported definition for a non-built-in embeddingModel before you
deploy. The model id is authored per step, so a flow can embed natively in one
step and require the seam in another.
Record storage and tenancy scoping
The record steps (upsert-record, get-record, list-records,
update-record) read and write through RuntimeConfig.recordStore. The runtime
ships InMemoryRecordStore for stateless agents and tests, and
HostedRecordStore, a client of the hosted POST /v1/runtime/records/*
endpoints. The default is NoOpRecordStore, whose methods throw an actionable
error so a record step fails loudly instead of silently dropping data.
An exported flow or agent that carries a tenancyStrategy needs two things from
the host, and neither happens automatically:
- A scope-capable store. Declare
supportsRecordScope = trueon yourRecordStoreand honor thescopedescriptor on every call: filter every read (including theupdatetarget lookup), stamp the scope columns on insert, and partition upsert conflict resolution by scope so the same(type, name)under two tenants stays two rows. - A resolved scope per execution. Resolve tenant and end-user identity
yourself and pass the result as
ExecuteAgentOptions.recordScopewhen callingexecuteAgent, or asExecuteFlowOptions.recordScopewhen callingexecuteFlowdirectly for a standalone flow export. The engine threads the agent-lane value onto the flows an agent runs internally, but a top-levelexecuteFlowcall must set the option itself; omitting it runs the flow in the plain owner namespace. Runtype’s identity resolution does not travel with an export, which is also whytenancyStrategymust be listed inhandledAgentConfigbefore the runtime will accept the agent.
If an execution carries a resolved scope but the wired store has not declared
the capability, the record step fails closed with RecordScopeUnsupportedError
rather than reading or writing the global namespace. HostedRecordStore
deliberately does not declare it: the hosted wire carries no scope descriptor,
so the server cannot verify which tenant the execution was admitted under.
A deployment with no tenancy strategy is unaffected. An omitted scope means the plain owner namespace, exactly as before.
Collection schema validation
If a record type is registered as a collection with a metadata schema, the
upsert-record and update-record steps consult
RuntimeConfig.collectionValidator before every write. upsert-record merges
on conflict inside the store, so it validates the incoming payload (per item in
batch mode). update-record reads the target row first, so it validates the
post-merge metadata and its verdict is exact.
The seam has two methods:
validate(type, metadata)returns the verdict for writingmetadataas a record oftype. It never throws: a lookup failure degrades tounevaluated.prime(type)warms the collection lookup without validating anything. Call it before validating inside an open transaction, which is what theupsert-recordbatch path does. A cold lookup issued while a single-session driver holds a transaction open deadlocks.
An enforce verdict rejects the write, and the step’s errorHandling setting
governs what the flow does with the failure, exactly as it does for a
payload-limit violation. A warn verdict lets the write through and surfaces
schemaWarnings on the step metadata (the batch branch of upsert-record
reports no warnings, matching how Runtype’s own batch writes behave). Either
way the verdict rides the store’s schemaValid write field, so a store that
persists that field records the same stamp Runtype does. See Defining a
schema for the validation modes and
the shape of a violation.
There is no hosted /v1/runtime/* endpoint for collection validation, and the
hosted POST /v1/runtime/records/* endpoints do not evaluate collection
schemas server side. A self-hosted deployment that needs enforcement supplies
its own validator, backed by whatever collection definitions it controls.
If the seam is omitted, every verdict is unevaluated: writes proceed exactly
as they did before the seam existed and no schemaValid stamp is recorded.
This direction is deliberate, because validation is an owner-configured
contract on metadata shape rather than a security boundary. It does mean a
collection set to enforce in the dashboard is not enforced by an
exported flow or agent until you wire a validator.