SDKs

Go SDK

Integrate with FortyOne using the generated Go client and secure transport helpers.

Use github.com/complexus-tech/fortyone-go to call the FortyOne v1 API with generated request and response types. The client adds bearer authentication, targets the production API by default, retries safe reads, and includes helpers for pagination, idempotency, structured errors, and webhook verification.

Preview availability

The Go SDK is not yet tagged or available from the public Go module proxy. The examples below show the current preview API; installation instructions will be added with the first public release.

Create a client and list stories

import (
    "context"
    "log"
    "os"

    fortyone "github.com/complexus-tech/fortyone-go"
    "github.com/google/uuid"
)

func listStories(ctx context.Context) error {
    client, err := fortyone.New(fortyone.Config{
        Token: os.Getenv("FORTYONE_TOKEN"),
    })
    if err != nil {
        return err
    }

    workspaceID, err := uuid.Parse(os.Getenv("FORTYONE_WORKSPACE_ID"))
    if err != nil {
        return err
    }

    pager, err := fortyone.NewStoryPager(
        client,
        workspaceID,
        fortyone.StoryPaginationOptions{Limit: 100},
    )
    if err != nil {
        return err
    }

    for {
        page, ok, err := pager.NextPage(ctx)
        if err != nil {
            return err
        }
        if !ok {
            return nil
        }
        for _, story := range page.Data {
            log.Printf("story id=%s reference=%s", story.Id, story.Reference)
        }
    }
}

fortyone.New targets https://api.fortyone.app unless you explicitly set Config.BaseURL for an approved environment. The workspace ID is not a URL or client setting. It identifies the workspace whose resources you want to access because FortyOne API operations are workspace-scoped.

Create a story idempotently

Generate one key for each logical write and persist it with the request before the first attempt. Do not generate another key inside a retry loop.

key, err := fortyone.NewIdempotencyKey()
if err != nil {
    return err
}

request := fortyone.CreateStoryRequest{
    Title:  "Investigate latency",
    TeamId: teamID,
}

// Persist key and request before the first network attempt.
response, err := client.CreateStoryWithResponse(
    ctx,
    workspaceID,
    &fortyone.CreateStoryParams{IdempotencyKey: key},
    request,
)
if err != nil {
    // The outcome is unknown. Retry with the same key and unchanged request.
    return err
}
if response.JSON201 == nil {
    if response.HTTPResponse == nil {
        return errors.New("FortyOne returned no HTTP response")
    }
    return fortyone.NewAPIError(
        response.StatusCode(),
        response.HTTPResponse.Header,
        response.Body,
    )
}

log.Printf(
    "story created id=%s reference=%s",
    response.JSON201.Data.Id,
    response.JSON201.Data.Reference,
)

NewIdempotencyKey returns 32 cryptographically random bytes as 64 hexadecimal characters. Use ValidateIdempotencyKey when restoring a retained key. Keep the SDK version and request encoding unchanged across attempts because the API compares the exact serialized JSON bytes.

The create operation accepts a personal access token or service-account key with stories:write. Use separate clients when reads and writes use different least-privilege credentials.

The default HTTP client has a 30-second timeout, refuses redirects, and uses a maximum of three attempts for safe reads. Set RetryPolicy{MaxAttempts: 1} to disable retries. The SDK never retries writes. For an ambiguous network failure or a documented retryable response, retain and reuse the same key and request, honor Retry-After, and apply a bounded retry budget. See Idempotent writes before implementing recovery.

Verify a webhook

NewWebhookVerifier accepts a show-once whsec_ secret and validates rotation signatures, the timestamp window, the delivery UUID, and the exact raw body. After verification, record the delivery ID and payload durably before returning 2xx. Do not log the signing secret, signature, or raw payload.

On this page