RESOURCES

Go SDK

SDK to integrate Phase in server-side applications running Go.

Prerequisites


Install the SDK

Install the SDK using go get.

Install

go get github.com/phasehq/golang-sdk/v2/phase

Import the SDK

Import the SDK in your Go files to start using its features.

import "github.com/phasehq/golang-sdk/v2/phase"

Initialize the SDK

Before interacting with the Phase service, initialize the SDK with your service token and the host information.

Parameters:

  • token type string: Your Phase Service Token (pss_service:v2:...) or User Token (pss_user:v1:...)
  • host type string: The URL of the Phase Console instance. Defaults to https://console.phase.dev if empty.
  • debug type bool: Setting to true will result in a higher level of log verbosity useful when debugging
package main

import (
    "log"
    "github.com/phasehq/golang-sdk/v2/phase"
)

func main() {
    token := "pss_service:v2:....."
    host := "https://console.phase.dev" // Adjust this for a self-hosted instance of Phase
    debug := false // For logging verbosity, disable in production

    p, err := phase.New(token, host, debug)
    if err != nil {
        log.Fatalf("Failed to initialize Phase client: %v", err)
    }

    _ = p // Use the client to manage secrets: p.Get(...), p.Create(...), etc.
}

Usage

You can get the AppID by going to your application settings in the Phase Console, hovering over UUID under the App section and clicking the Copy button:

hello world

Secret Types

Phase supports three secret types, available as constants:

ConstantValueDescription
phase.SecretTypeSecret"secret"Default — standard encrypted secret
phase.SecretTypeSealed"sealed"Write-only in the Phase Console — value is hidden in the Console UI after creation (still returned by the SDK / API)
phase.SecretTypeConfig"config"Non-sensitive configuration value

Use phase.ValidateSecretType(t) to validate a type string — returns nil for valid types (including empty string, which defaults to "secret").

Secret Lifecycle

The Lifecycle field on SecretResult indicates how a secret's value is managed, available as constants:

ConstantValueDescription
phase.SecretLifecycleStatic"static"Default — the value is managed by the user
phase.SecretLifecycleRotating"rotating"The value is automatically rotated by the Phase rotation engine

Secrets with a rotating lifecycle are managed by the Phase rotation engine and cannot be updated or deleted through the SDK — manage them via the rotating-secret controls in the Console. See Rotating Secrets. Dynamic secrets (IsDynamic: true) are leased credentials and carry no lifecycle value.

Creating Secrets

Define key-value pairs and specify the Environment, App name or ID, and path (optional) to create new Secrets.

Options:

type CreateOptions struct {
    KeyValuePairs []phase.KeyValuePair
    EnvName       string
    AppID         string
    AppName       string // Alternative to AppID
    Path          string
    OverrideValue string
    Type          string // "secret" (default), "sealed", or "config" — applied to all pairs when KeyValuePair.Type is empty
}

type KeyValuePair struct {
    Key     string
    Value   string
    Type    string   // Per-pair type override; takes precedence over CreateOptions.Type
    Comment string   // Optional comment
    Tags    []string // Optional tag names; tags that do not exist yet are created
}
err := p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "API_KEY", Value: "api_secret"},
        {Key: "DB_HOST", Value: "localhost:5432"},
    },
    EnvName: "Production",
    AppID:   "app-id-here",  // Or use AppName: "MyApp"
    Path:    "/api/keys",    // Optional, default path: /
})
if err != nil {
    log.Fatalf("Failed to create secret: %v", err)
}

// Create a sealed secret (value hidden in the Console after creation)
err = p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "STRIPE_KEY", Value: "sk_live_..."},
    },
    EnvName: "Production",
    AppID:   "app-id-here",
    Type:    phase.SecretTypeSealed,
})

// Create config values (non-sensitive)
err = p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "APP_PORT", Value: "8080"},
        {Key: "LOG_LEVEL", Value: "info"},
    },
    EnvName: "Production",
    AppID:   "app-id-here",
    Type:    phase.SecretTypeConfig,
})

Getting Secrets

Provide the Environment name, App name or ID, and optionally filter by specific keys, tags, and path.

Options:

type GetOptions struct {
    EnvName                        string
    AppID                          string
    AppName                        string   // Alternative to AppID
    Keys                           []string // Optional: filter by specific key names
    Tag                            string   // Optional: partial, case-insensitive tag match (client-side)
    Tags                           []string // Optional: exact tag names, any match (server-side)
    Path                           string   // Optional: filter by path; empty returns secrets from all paths
    Dynamic                        bool     // Optional: include dynamic secrets
    Lease                          bool     // Optional: generate leases for dynamic secrets
    LeaseTTL                       *int     // Optional: lease TTL in seconds
    Raw                            bool     // Optional: skip ${REF} reference resolution
    FailOnReferenceResolutionError bool     // Optional: error if any ${REF} fails to resolve (default: leave it unresolved)
}

Each secret is returned as a SecretResult:

type SecretResult struct {
    Key          string
    Value        string
    Comment      string
    Path         string
    Type         string       // "secret", "sealed", or "config"
    Lifecycle    string       // "static" (default) or "rotating" — see Secret Lifecycle
    Application  string
    Environment  string
    Tags         []string
    Overridden   bool         // true if a personal override is active
    IsDynamic    bool         // true for dynamic secrets
    DynamicGroup string       // provider group label for dynamic secrets
    ID           string       // server-side identifier of the secret
    Version      int
    CreatedAt    string
    UpdatedAt    string
}

Get all secrets

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here", // Or use AppName: "MyApp"
})
if err != nil {
    log.Fatalf("Failed to get secrets: %v", err)
}
for _, s := range secrets {
    log.Printf("%s=%s", s.Key, s.Value)
}

Get specific keys

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Keys:    []string{"API_KEY", "DB_HOST"},
})

Filter by tag and path

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Tag:     "backend",
    Path:    "/api/config",
})

Include dynamic secrets with leases

secrets, err := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Dynamic: true,
    Lease:   true,
})

Updating a Secret

Provide the new value along with the environment name, application name or ID, and key to update an existing secret.

Options:

type UpdateOptions struct {
    EnvName         string
    AppID           string
    AppName         string   // Alternative to AppID
    ID              string   // Optional: match the secret by ID instead of Key; Key then renames it
    Key             string
    Value           string   // Empty keeps the current value unless AllowEmptyValue is set
    AllowEmptyValue bool     // Write an empty Value instead of keeping the current one
    SourcePath      string   // Path where the secret currently lives
    DestinationPath string   // Optional: move the secret to a new path
    Override        bool     // Set a personal override
    ToggleOverride  bool     // Toggle personal override on/off
    Type            string   // "secret", "sealed", or "config" — leave empty to keep existing type
    Comment         *string  // nil keeps the comment; an empty string clears it
    Tags            []string // nil keeps the tags; an empty slice clears them
}
err := p.Update(phase.UpdateOptions{
    EnvName: "Production",
    AppID:   "app-id-here", // Or use AppName: "MyApp"
    Key:     "API_KEY",
    Value:   "my_updated_api_secret",
})
if err != nil {
    log.Fatalf("Failed to update secret: %v", err)
}

Deleting Secrets

Specify the environment name, application name or ID, keys to delete, and optionally the path.

Options:

type DeleteOptions struct {
    EnvName      string
    AppID        string
    AppName      string   // Alternative to AppID
    KeysToDelete []string
    Path         string
    IDs          []string // Optional: secret IDs to delete directly, without a key lookup
}
keysNotFound, err := p.Delete(phase.DeleteOptions{
    EnvName:      "Production",
    AppID:        "app-id-here", // Or use AppName: "MyApp"
    KeysToDelete: []string{"API_KEY", "OLD_SECRET"},
    Path:         "/api/keys", // Optional, default path: /
})
if err != nil {
    log.Fatalf("Failed to delete secret: %v", err)
}
if len(keysNotFound) > 0 {
    log.Printf("Keys not found: %v", keysNotFound)
}

Secret References

Get() automatically resolves ${REF} syntax in secret values before returning results. This includes same-environment (${KEY}), cross-environment (${staging.SECRET_KEY}), cross-app (${backend_api::production.API_KEY}), and path-scoped (${/backend/config/DB_HOST}) references.

// References are resolved automatically — no extra steps needed
secrets, _ := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
})

for _, s := range secrets {
    // s.Value already has all ${REF} references resolved
    fmt.Printf("%s=%s\n", s.Key, s.Value)
}

To get raw, unresolved values (e.g. for display or inspection), set Raw: true:

secrets, _ := p.Get(phase.GetOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    Raw:     true,
})

for _, s := range secrets {
    // s.Value contains the original ${REF} syntax, not the resolved value
    fmt.Printf("%s=%s\n", s.Key, s.Value)
}

Fail on unresolved references

By default, if a reference can't be resolved — the referenced secret doesn't exist, was mistyped, or the lookup fails due to a network, rate-limit, or permissions error — the original ${REF} text is left in place and resolution continues. This is convenient for inspection tooling, but risky for applications that need fully-resolved configuration.

Set FailOnReferenceResolutionError: true to fail fast instead. Get() then returns an error the moment any reference can't be resolved, so you never load an unresolved ${...} reference string into your app's config:

secrets, err := p.Get(phase.GetOptions{
    EnvName:                        "Production",
    AppID:                          "app-id-here",
    FailOnReferenceResolutionError: true,
})
if err != nil {
    // e.g. a referenced secret was deleted, mistyped, or the lookup was rate-limited
    log.Fatalf("Failed to resolve secret references: %v", err)
}

Overrides

Create or update a personal override for a secret:

// Create a secret with an override value
err := p.Create(phase.CreateOptions{
    KeyValuePairs: []phase.KeyValuePair{
        {Key: "API_URL", Value: "https://api.example.com"},
    },
    EnvName:       "Development",
    AppID:         "app-id-here",
    OverrideValue: "http://localhost:3000",
})
if err != nil {
    log.Fatalf("Failed to create secret: %v", err)
}

// Update override for existing secret
err = p.Update(phase.UpdateOptions{
    EnvName:  "Development",
    AppID:    "app-id-here",
    Key:      "API_URL",
    Value:    "http://localhost:4000",
    Override: true,
})
if err != nil {
    log.Fatalf("Failed to update override: %v", err)
}

// Toggle override on/off
err = p.Update(phase.UpdateOptions{
    EnvName:        "Development",
    AppID:          "app-id-here",
    Key:            "API_URL",
    ToggleOverride: true,
})
if err != nil {
    log.Fatalf("Failed to toggle override: %v", err)
}

Secret metadata

Each SecretResult also carries the server-side ID, Version, CreatedAt and UpdatedAt of the secret, so callers that track state (for example the Terraform provider) can target a secret by ID:

comment := "Rotated monthly"
err := p.Update(phase.UpdateOptions{
    EnvName: "Production",
    AppID:   "app-id-here",
    ID:      secret.ID,
    Key:     "API_KEY_V2",          // optional new key name; leave empty to keep it
    Comment: &comment,              // nil leaves the comment unchanged, "" clears it
    Tags:    []string{"backend"},   // nil leaves the tags unchanged, an empty slice clears them
})

KeyValuePair accepts Comment and Tags on create, and GetOptions.Tags filters by exact tag names server-side (secrets carrying any of them are returned).

Management API

Apps, environments, roles, members, invites and service accounts can be managed with the same token through the Phase REST API. These methods exchange plain JSON: the server handles all key wrapping, so apps created this way use server-side encryption, which the environment and access methods require. A service account token (pss_service:v2) or personal access token (pss_user) is needed, with a role that grants the corresponding permissions.

// Apps and environments
app, err := p.CreateApp(phase.CreateAppOptions{Name: "backend"}) // Development, Staging and Production
apps, err := p.ListApps()
env, err := p.CreateEnvironment(app.ID, "qa") // custom environments require a paid plan
envs, err := p.ListEnvironments(app.ID)
err = p.DeleteApp(app.ID)

// Service accounts, access and tokens
roles, err := p.ListRoles()
sa, err := p.CreateServiceAccount(phase.CreateServiceAccountOptions{Name: "ci", RoleID: roles[0].ID})
fmt.Println(sa.InitialToken.Token) // the full pss_service:v2 token is only returned once
_, err = p.SetServiceAccountAccess(sa.ID, []phase.AppAccessInput{{ID: app.ID, EnvironmentIDs: []string{envs[0].ID}}})
token, err := p.CreateServiceAccountToken(sa.ID, phase.CreateServiceAccountTokenOptions{Name: "deploy", ExpiresIn: 86400})
err = p.DeleteServiceAccountToken(sa.ID, token.ID)

// Members and invites
invite, err := p.CreateInvite(phase.CreateInviteOptions{Email: "dev@example.com", RoleID: roles[0].ID})
members, err := p.ListMembers()
_, err = p.SetMemberAccess(members[0].ID, []phase.AppAccessInput{{ID: app.ID, EnvironmentIDs: []string{envs[0].ID}}})

Available methods: ListApps, CreateApp, GetApp, UpdateApp, DeleteApp, ListEnvironments, CreateEnvironment, GetEnvironment, UpdateEnvironment, DeleteEnvironment, ListRoles, GetRole, ListMembers, GetMember, UpdateMemberRole, DeleteMember, GetMemberAccess, SetMemberAccess, ListInvites, CreateInvite, DeleteInvite, ListServiceAccounts, CreateServiceAccount, GetServiceAccount, UpdateServiceAccount, DeleteServiceAccount, SetServiceAccountAccess, CreateServiceAccountToken and DeleteServiceAccountToken. See the Public API reference for the fields of each object.