Skip to content

SDKs

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
go get github.com/managed-dev/forge-go

Construct a client with an API key, then call a resource. The example lists your sites — a sites:read call — and prints each one.

main.go
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)
}
}

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:

deploy-and-wait.go
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)

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:

configured-client.go
client := forge.New(
os.Getenv("FORGE_TOKEN"),
forge.WithVersion("2026-06-23"),
forge.WithMaxRetries(3),
)