@molecule/api-model-hosting-koyeb
Provider bond · model-hosting · API (Node) · v1.0.1 · Apache-2.0
Koyeb model hosting for molecule.dev — run a model server container on CPU or an RTX 4000 / L4 GPU in the EU or US, with sleep when idle
npm install @molecule/api-model-hosting-koyebnpm · Source on GitHub · Implements @molecule/api-model-hosting
How it works
@molecule/api-model-hosting-koyeb is a provider bond on the API (Node) side: it implements the model-hosting core interface (@molecule/api-model-hosting) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider, requireProvider } from '@molecule/api-model-hosting'
import { provider } from '@molecule/api-model-hosting-koyeb'
setProvider(provider) // reads KOYEB_API_TOKEN on first use
const endpoint = await requireProvider().deploy({
name: 'laya',
image: 'ghcr.io/acme/laya-serve:0.3.1',
port: 8000,
healthPath: '/health',
secretEnv: { LAYA_API_KEY: process.env.LAYA_API_KEY ?? '' },
accelerator: { kind: 'cpu' },
region: 'eu',
scaling: { minInstances: 0, maxInstances: 2, idleTimeoutSeconds: 600 },
access: 'private',
serverEnforcesAuth: true,
})
endpoint.url // https://laya-acme.koyeb.appWorks with: @molecule/api-bond, @molecule/api-i18n, @molecule/api-model-hosting, @molecule/api-secrets
Secrets: KOYEB_API_TOKEN
Reference
Koyeb model hosting for molecule.dev — run a model server container on a CPU instance or an NVIDIA GPU (L4, A100, H100, H200) in the EU or US, with sleep-when-idle and per-second billing.
Each endpoint is one Koyeb app holding one WEB service, reachable at the
app's *.koyeb.app domain.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, requireProvider } from '@molecule/api-model-hosting'
import { provider } from '@molecule/api-model-hosting-koyeb'
setProvider(provider) // reads KOYEB_API_TOKEN on first use
const endpoint = await requireProvider().deploy({
name: 'laya',
image: 'ghcr.io/acme/laya-serve:0.3.1',
port: 8000,
healthPath: '/health',
secretEnv: { LAYA_API_KEY: process.env.LAYA_API_KEY ?? '' },
accelerator: { kind: 'cpu' },
region: 'eu',
scaling: { minInstances: 0, maxInstances: 2, idleTimeoutSeconds: 600 },
access: 'private',
serverEnforcesAuth: true,
})
endpoint.url // https://laya-acme.koyeb.app
Type
provider
Installation
npm install @molecule/api-model-hosting-koyeb @molecule/api-bond @molecule/api-i18n @molecule/api-model-hosting @molecule/api-secrets
API
Interfaces
KoyebConfig
Configuration for the Koyeb model hosting provider.
interface KoyebConfig {
/** API token. Defaults to `KOYEB_API_TOKEN`. */
apiToken?: string
/** Region for `'us'`. Default `'was'` (Washington D.C.). */
usRegion?: string
/** Region for `'eu'`. Default `'fra'` (Frankfurt). */
euRegion?: string
/** CPU instance type when `accelerator` is CPU. Default `'large'` (4 vCPU / 4 GB). */
cpuInstanceType?: string
/** API base URL (tests, proxies). Default `https://app.koyeb.com`. */
apiBaseUrl?: string
/** How often `deploy` polls the service while it rolls out, in ms. Default 5000. */
pollIntervalMs?: number
}
KoyebDefinition
A Koyeb deployment definition (only the fields written here).
interface KoyebDefinition {
name: string
type: 'WEB'
docker: { image: string; entrypoint?: string[]; args?: string[] }
env: Array<{ key: string; value: string }>
ports: Array<{ port: number; protocol: 'http' }>
routes: Array<{ port: number; path: string }>
regions: string[]
instance_types: Array<{ type: string }>
scalings: Array<{
min: number
max: number
targets?: Array<{
sleep_idle_delay?: { value?: number }
concurrent_requests?: { value?: number }
}>
}>
health_checks: Array<{ grace_period?: number; http: { port: number; path: string } }>
}
KoyebApp
A Koyeb app (only the fields read here).
interface KoyebApp {
id: string
name: string
domains?: Array<{ name?: string }>
}
KoyebService
A Koyeb service (only the fields read here).
interface KoyebService {
id: string
name: string
app_id: string
created_at?: string
status?:
| 'STARTING'
| 'HEALTHY'
| 'DEGRADED'
| 'UNHEALTHY'
| 'DELETING'
| 'DELETED'
| 'PAUSING'
| 'PAUSED'
| 'RESUMING'
messages?: string[]
latest_deployment_id?: string
}
Classes
KoyebApiError
A Koyeb API error, carrying the HTTP status and Koyeb's error code.
class KoyebApiError extends Error {
/** HTTP status. */
readonly status: number
/** Koyeb error code, when given. */
readonly code: string | undefined
/**
* Creates the error.
*
* @param message - What failed.
* @param status - HTTP status.
* @param code - Koyeb error code.
*/
constructor(message: string, status: number, code?: string) {
super(message)
this.name = 'KoyebApiError'
this.status = status
this.code = code
}
}
KoyebClient
Thin Koyeb API client.
class KoyebClient {
/**
* Creates the client.
*
* @param token - Returns the API token (read lazily).
* @param baseUrl - API base URL.
*/
constructor(
private readonly token: () => string,
private readonly baseUrl: string,
) {}
/**
* Sends one request; `null` on 404.
*
* @param method - HTTP method.
* @param path - Path under the base URL.
* @param body - JSON body.
* @returns The parsed body, or `null` on 404.
*/
private async call<T>(method: string, path: string, body?: unknown): Promise<T | null> {
const res = await fetch(`${this.baseUrl}${path}`, {
method,
headers: { authorization: `Bearer ${this.token()}`, 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
})
if (res.status === 404) return null
const json = (await res.json().catch(() => ({}))) as Record<string, unknown>
if (!res.ok) {
throw new KoyebApiError(
`Koyeb ${method} ${path} failed (${res.status}): ${String(json.message ?? json.error ?? 'no message')}`,
res.status,
typeof json.code === 'string' ? json.code : undefined,
)
}
return json as T
}
/**
* Finds an app by exact name.
*
* @param name - App name.
* @returns The app, or `null`.
*/
async findApp(name: string): Promise<KoyebApp | null> {
const res = await this.call<{ apps?: KoyebApp[] }>(
'GET',
`/v1/apps?name=${encodeURIComponent(name)}`,
)
return res?.apps?.find((a) => a.name === name) ?? null
}
/**
* Reads one app.
*
* @param id - App id.
* @returns The app, or `null`.
*/
async getApp(id: string): Promise<KoyebApp | null> {
return (await this.call<{ app?: KoyebApp }>('GET', `/v1/apps/${id}`))?.app ?? null
}
/**
* Creates an app.
*
* @param name - App name.
* @returns The app.
*/
async createApp(name: string): Promise<KoyebApp> {
const res = await this.call<{ app: KoyebApp }>('POST', '/v1/apps', { name })
return res!.app
}
/**
* Deletes an app (and with it, its services).
*
* @param id - App id.
*/
async deleteApp(id: string): Promise<void> {
await this.call('DELETE', `/v1/apps/${id}`)
}
/**
* Finds a service by app and name.
*
* @param appId - App id.
* @param name - Service name.
* @returns The service, or `null`.
*/
async findService(appId: string, name: string): Promise<KoyebService | null> {
const res = await this.call<{ services?: KoyebService[] }>(
'GET',
`/v1/services?app_id=${encodeURIComponent(appId)}&name=${encodeURIComponent(name)}`,
)
return res?.services?.find((s) => s.name === name) ?? null
}
/**
* Lists every service in the organization.
*
* @returns The services.
*/
async listServices(): Promise<KoyebService[]> {
const out: KoyebService[] = []
for (let offset = 0; ; offset += 100) {
const res = await this.call<{ services?: KoyebService[]; has_next?: boolean }>(
'GET',
`/v1/services?limit=100&offset=${offset}`,
)
out.push(...(res?.services ?? []))
if (!res?.has_next) return out
}
}
/**
* Reads one service.
*
* @param id - Service id.
* @returns The service, or `null`.
*/
async getService(id: string): Promise<KoyebService | null> {
return (
(await this.call<{ service?: KoyebService }>('GET', `/v1/services/${id}`))?.service ?? null
)
}
/**
* Creates a service in an app.
*
* @param appId - App id.
* @param definition - Deployment definition.
* @returns The service.
*/
async createService(appId: string, definition: KoyebDefinition): Promise<KoyebService> {
const res = await this.call<{ service: KoyebService }>('POST', '/v1/services', {
app_id: appId,
definition,
})
return res!.service
}
/**
* Reads the definition a deployment was made from (`GET /v1/deployments/{id}`).
*
* @param id - Deployment id (a service's `latest_deployment_id`).
* @returns The definition, or `null`.
*/
async getDefinition(id: string): Promise<KoyebDefinition | null> {
const res = await this.call<{ deployment?: { definition?: KoyebDefinition } }>(
'GET',
`/v1/deployments/${id}`,
)
return res?.deployment?.definition ?? null
}
/**
* Replaces a service's definition (a new deployment rolls out).
*
* @param id - Service id.
* @param definition - Deployment definition.
* @returns The service.
*/
async updateService(id: string, definition: KoyebDefinition): Promise<KoyebService> {
const res = await this.call<{ service: KoyebService }>('PUT', `/v1/services/${id}`, {
definition,
})
return res!.service
}
}
Functions
instanceTypeFor(spec, cpuType)
Picks the Koyeb instance type for a spec.
function instanceTypeFor(spec: ModelEndpointSpec, cpuType: string): string
spec— The endpoint spec.cpuType— The CPU instance type to use for CPU specs.
Returns: A Koyeb instance type id.
toDefinition(spec, region, cpuType)
Builds the Koyeb deployment definition for a spec.
function toDefinition(spec: ModelEndpointSpec, region: string, cpuType: string): KoyebDefinition
spec— The endpoint spec.region— Koyeb region id.cpuType— CPU instance type for CPU specs.
Returns: The definition.
statusOf(status)
Maps a Koyeb service status to an endpoint status.
function statusOf(
status:
| 'STARTING'
| 'HEALTHY'
| 'DEGRADED'
| 'UNHEALTHY'
| 'DELETING'
| 'DELETED'
| 'PAUSING'
| 'PAUSED'
| 'RESUMING'
| undefined,
): EndpointStatus
status— KoyebService.status.
Returns: The endpoint status.
createProvider(config)
Creates a Koyeb model hosting provider.
function createProvider(config?: KoyebConfig): ModelHostingProvider
config— API token, region mapping, CPU instance type.
Returns: A ModelHostingProvider backed by Koyeb.
Constants
KOYEB_API
Default API base URL.
const KOYEB_API: 'https://app.koyeb.com'
KOYEB_GPUS
GPU instance ids by memory, smallest first (koyeb.com/docs/reference/instances, 2026-09-30).
const KOYEB_GPUS: readonly { id: string; vramGb: number }[]
KOYEB_CAPABILITIES
What Koyeb offers here.
const KOYEB_CAPABILITIES: ProviderCapabilities
provider
The provider implementation — lazy, so env vars are read on first use.
const provider: ModelHostingProvider
modelHostingKoyebSecretDefinitions
Secret definitions used by the Koyeb model hosting bond.
const modelHostingKoyebSecretDefinitions: SecretDefinition[]
Core Interface
Implements @molecule/api-model-hosting interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-model-hosting'
import { provider } from '@molecule/api-model-hosting-koyeb'
export function setupModelHostingKoyeb(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1@molecule/api-model-hosting^1.0.0@molecule/api-secrets^1.0.1
Environment Variables
KOYEB_API_TOKEN(required) — koyeb api token- Setup: Koyeb API token — An organization API token (Settings → API). GPU instances need a paid plan and a card on file.
- Get it here: https://app.koyeb.com/user/settings/api
- Example:
koyeb-api-token
Runtime Dependencies
-
@molecule/api-bond -
@molecule/api-i18n -
@molecule/api-model-hosting -
@molecule/api-secrets -
No endpoint auth of its own. A Koyeb URL is public. For
access: 'private'you must setserverEnforcesAuth: true, pass the server's key insecretEnv(e.g.LAYA_API_KEY), and give the consumer the same key (apiKey).authHeaders()always returns{}. -
secretEnvis stored as plain service env, visible in the Koyeb console to anyone in the organization. Use Koyeb secrets yourself for anything stricter. -
Instance types: CPU uses
large(4 vCPU / 4 GB — enough for a ~400M encoder;mediumhas too little memory) unlesscpuInstanceTypeis set. GPUs are picked byminVramGbfromgpu-nvidia-l4(24 GB) up; passaccelerator.modelto name one exactly (e.g. an RTX 4000 SFF Ada id from Koyeb's catalog). Which regions carry which GPUs is not documented on the regions page — a GPU in a region without it fails the deploy. -
Sleep:
minInstances: 0sleeps afteridleTimeoutSeconds(300–43,200 s; the upper end depends on your plan) and wakes in 1–5 s plus the model load.'us'→was,'eu'→fra. -
GPU instances need a paid plan and a card on file (Koyeb holds a pre-authorization); the deploy fails with Koyeb's message otherwise.
-
remove()deletes the whole app.list()returns services whose app has the same name — the shape this bond creates. -
API errors throw
KoyebApiErrorwith the HTTPstatusand Koyeb'scode.