Typed everywhere
Every resource, parameter, and the response envelope are generated types. Your editor autocompletes scopes, fields, and enum values.
Preview The official SDKs give you a typed,
idiomatic client for the managed.dev API in your
language of choice. There’s a Go, a TypeScript, and a PHP SDK, and each
is generated from the same OpenAPI 3.1 spec that drives
the mf CLI and the Terraform provider.
Because every client comes off one spec, the SDKs never drift from the API — a new
endpoint or field lands in all of them at once.
The SDKs are thin, typed wrappers over https://api.managed.dev/v1 — not a second
product with its own concepts. Everything you can do with cURL you can do with an
SDK, plus the ergonomics you’d expect from a PaaS client:
Typed everywhere
Every resource, parameter, and the response envelope are generated types. Your editor autocompletes scopes, fields, and enum values.
Auto-pagination
Cursor pagination is handled for you — iterate a list and the SDK fetches each page transparently as you go.
Idempotent retries
The client retries transient failures with backoff and attaches an
Idempotency-Key to writes, so a retry replays
the original job instead of creating a second one.
Typed errors
The error envelope is surfaced as typed exceptions
carrying type, code, param, request_id, and doc_url — match on the
code, don’t parse strings.
Job waiting
Helpers wait on an async job to reach a terminal state, picking SSE or ETag long-poll automatically for your environment.
Pinned version
The client sends a Forge-Version header it was generated against, so an
additive API change never silently shifts a payload under you.
go get github.com/managed-dev/forge-gonpm install @managed-dev/forgecomposer require managed-dev/forgeConstruct a client with an API key, then call a resource. The
example lists your sites — a sites:read call — and prints each one.
package main
import ( "context" "fmt" "os"
forge "github.com/managed-dev/forge-go")
func main() { client := forge.New(os.Getenv("FORGE_TOKEN"))
// Auto-pagination: iterate every site without managing cursors. iter := client.Sites.List(context.Background(), forge.SitesListParams{}) for iter.Next() { site := iter.Current() fmt.Printf("%s %s runtime=%s\n", site.ID, site.Name, site.Runtime) } if err := iter.Err(); err != nil { var apiErr *forge.Error if forge.As(err, &apiErr) { fmt.Printf("api error %s (%s): %s\n", apiErr.Code, apiErr.Type, apiErr.Message) } os.Exit(1) }}import { Forge } from "@managed-dev/forge";
const client = new Forge({ token: process.env.FORGE_TOKEN! });
// `for await` drives auto-pagination across cursors.for await (const site of client.sites.list()) { console.log(site.id, site.name, `runtime=${site.runtime}`);}<?phprequire 'vendor/autoload.php';
use ManagedDev\Forge\Client;use ManagedDev\Forge\ApiError;
$client = new Client(getenv('FORGE_TOKEN'));
try { // Iterating the pager fetches each page on demand. foreach ($client->sites->list() as $site) { printf("%s %s runtime=%s\n", $site->id, $site->name, $site->runtime); }} catch (ApiError $e) { fprintf(STDERR, "api error %s (%s): %s\n", $e->code, $e->type, $e->message); exit(1);}Most mutations return 202 Accepted and a job. The
job-waiting helper blocks until the job succeeds or fails, choosing the right
transport (SSE for interactive use, ETag long-poll in CI) without you thinking about
it. This deploys an environment and waits:
job, err := client.Environments.Deploy(ctx, envID, forge.DeployParams{ ArtifactID: artifactID,})if err != nil { return err}
// Blocks until the job reaches a terminal state, polling/streaming for you.done, err := client.Jobs.Wait(ctx, job.ID)if err != nil { return err // includes a failed-job error with job.Error populated}fmt.Printf("deploy %s: %s\n", done.ID, done.Status)const job = await client.environments.deploy(envId, { artifactId });
// Resolves when the job succeeds; throws a JobFailedError otherwise.const done = await client.jobs.wait(job.id);console.log(`deploy ${done.id}: ${done.status}`);$job = $client->environments->deploy($envId, ['artifact_id' => $artifactId]);
// Blocks until terminal; throws JobFailedError on failure.$done = $client->jobs->wait($job->id);printf("deploy %s: %s\n", $done->id, $done->status);Common options are uniform across languages — base URL (for testing against a mock), API version pin, request timeout, retry budget, and an IP-allowlisted key all work the same way. A typical configured client looks like this:
client := forge.New( os.Getenv("FORGE_TOKEN"), forge.WithVersion("2026-06-23"), forge.WithMaxRetries(3),)const client = new Forge({ token: process.env.FORGE_TOKEN!, version: "2026-06-23", maxRetries: 3,});$client = new Client(getenv('FORGE_TOKEN'), [ 'version' => '2026-06-23', 'maxRetries' => 3,]);