Skip to content

valksor/fal-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

fal-go

CI Coverage Status Go Reference Go Report Card License

A Go client for fal.ai, a 1:1 behavioral port of the official Python client (fal-client). Same wire protocol, same method surface, idiomatic Go ergonomics: one *fal.Client, context.Context on every call, functional options, and iter.Seq2 iterators.

Requires Go 1.26+.

go get github.com/valksor/fal-go

Getting a key

Create a key in the fal dashboard and expose it as FAL_KEY. Prefer a .env file or direnv over an inline FAL_KEY=… go run … (which lands in shell history):

echo 'FAL_KEY=your-key' > .env

Credentials are resolved in this order: explicit WithKey, FAL_KEY, FAL_KEY_ID + FAL_KEY_SECRET, then the ~/.fal/auth0_token login token (auto-refreshed).

Quick start

ctx := context.Background()
result, err := fal.Subscribe(ctx, "fal-ai/fast-sdxl",
    map[string]any{"prompt": "a cat riding a bicycle"},
    fal.WithLogs(true),
    fal.OnQueueUpdate(func(s fal.Status) {
        if q, ok := s.(fal.Queued); ok {
            log.Printf("queued at %d", q.Position)
        }
    }),
)

The package-level functions (fal.Subscribe, fal.Run, ...) delegate to a shared default client. Construct your own with fal.New(...) for custom keys, HTTP clients, or timeouts.

Result types

API results are dynamic JSON, returned as any (mirroring Python). Decode into a typed struct with the generic helper:

out, err := fal.As[MyResult](result)

Status values

Status is a sealed interface; the only cases are Queued, InProgress and Completed:

switch v := s.(type) {
case fal.Queued:     // v.Position
case fal.InProgress: // v.Logs
case fal.Completed:  // v.Metrics, v.Error
}

Streaming (SSE)

Stream returns an iter.Seq2[map[string]any, error]. Break on error — the error is yielded as the final pair, and breaking closes the connection:

for event, err := range c.Stream(ctx, "fal-ai/some-app", input) {
    if err != nil {
        log.Printf("stream error: %v", err)
        break
    }
    fmt.Println(event)
}

Uploads

Upload, UploadFile and UploadImage return a public URL. The default backend chain is the fal CDN (fal_v3) with a GCS (fal) fallback — i.e. ["fal_v3", "fal"], matching the Python client — and multipart for files over 100 MB. Configure the chain with WithRepository / WithFallbackRepository (valid values: "fal_v3" CDN, "fal" GCS; "cdn" is a deprecated alias for "fal_v3"). The next backend is tried on any failure of the previous one; WithFallbackRepository() with no arguments disables the default fallback. Large uploads routed to fal_v3 go multipart and are returned directly without falling back.

url, err := c.UploadFile(ctx, "input.png")

Realtime

Realtime opens a msgpack WebSocket connection; WSConnect is the lower-level raw-connection escape hatch.

conn, err := c.Realtime(ctx, "fal-ai/some-app")
defer conn.Close()
conn.Send(ctx, map[string]any{"prompt": "hi"})
msg, err := conn.Recv(ctx)

Security note: by default the realtime JWT is placed in the WebSocket URL query string (matching the Python client), which intermediaries may log. Use WithJWT(false) to send it as an Authorization header instead.

Working with handles

Subscribe and Run are the primary paths. For finer control, Submit returns a *RequestHandle you can poll yourself (handle.Status, handle.IterEvents, handle.Get, handle.Cancel). GetHandle(application, requestID) reconstructs a handle for a request you already have an id for.

Errors

Type Meaning
*fal.Error generic client error
*fal.HTTPError non-2xx API response (StatusCode, ErrorType; headers redacted)
*fal.TimeoutError Subscribe exceeded its client timeout
*fal.MissingCredentialsError no credentials could be resolved
*fal.RealtimeError server error frame on a realtime connection

Inspect with errors.As.

Package layout

The top-level fal package is the entry point — construct a *fal.Client (or use the package-level functions) and call its flat methods. Everything it returns and accepts is re-exported here, so importing github.com/valksor/fal-go alone is enough for normal use.

The implementation is organized into focused, public sub-packages that share one HTTP core; import them directly only if you want a narrower dependency:

Package Responsibility
transport HTTP engine: request building, retry/backoff, CDN token cache
auth credential resolution (key, key-id+secret, login-token refresh)
option functional options (WithX/OnX) and the resolved CallOptions
run, queue, stream, upload, realtime the feature operations
appid, status, storage, encode, errs leaf types and helpers (errs is so named to avoid shadowing the stdlib errors)

The fal.* names (fal.Status, fal.RequestHandle, fal.WithLogs, …) are aliases/re-exports of these packages' symbols, so they stay interchangeable — e.g. errors.As(err, new(*fal.HTTPError)) matches an error built inside transport.

The stable, supported surface is the top-level fal package. The sub-packages are public for narrower dependencies, but a few of their symbols are plumbing rather than API: build a client only with fal.New (do not construct transport.Core yourself), and treat option.CallOptions as an internal resolution type — configure calls with the fal.WithX options, not by building a CallOptions directly.

Migrating from the Python fal-client

Python Go
fal_client.run(app, args) fal.Run(ctx, app, args)
fal_client.submit(app, args) fal.Submit(ctx, app, args)
fal_client.subscribe(app, args, on_queue_update=fn) fal.Subscribe(ctx, app, args, fal.OnQueueUpdate(fn))
fal_client.status(app, id, with_logs=True) fal.StatusOf(ctx, app, id, true)
fal_client.result(app, id) fal.Result(ctx, app, id)
fal_client.cancel(app, id) fal.Cancel(ctx, app, id)
fal_client.stream(app, args) for ev, err := range fal.Stream(ctx, app, args)
fal_client.upload_file(path) fal.UploadFile(ctx, path)
fal_client.realtime(app) fal.Realtime(ctx, app)
keyword args (path=, hint=, ...) options (fal.WithPath, fal.WithHint, ...)
Queued / InProgress / Completed same, as a sealed Status interface

The module-level status() is renamed StatusOf in Go because Status is also the result type name.

About

Go client library for the fal.ai API

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages