For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
SupportDashboard
Getting StartedAPI ReferenceRoadmapBlog
Getting StartedAPI ReferenceRoadmapBlog
  • Getting Started
    • Overview
    • What is Schematic?
    • Concepts
  • Using Schematic
    • Who Uses Schematic
  • Quickstart
    • Quickstart
    • Account Setup
    • Entitling a Feature
    • Tracking Usage
    • Components
    • Identifying Users
    • Setup the SDK
  • Using Feature Flags
    • Overview
    • Flags
    • Features
    • Tracking Feature Usage
    • Company Overrides
    • Feature Types
  • Building Your Catalog
    • Overview
    • Plans
    • Managing Company Plans
    • Configuring the Catalog
    • Add Ons
    • Trials
  • AI Tooling
    • For Developers
  • Setting Up Billing
    • Overview
    • Usage Based Billing Models
    • Seat Based Billing Models
    • Credit burndown
  • Using UI Components
    • Overview
  • Developer Resources
    • Concepts
    • Key Management
    • Environments
    • Entity Relationship Diagram
      • Overview
      • Cross-platform Features
      • React
      • React Native
      • Vue
      • Angular
      • Nextjs
      • Javascript (Client-side)
      • Go
      • Node.js
      • Python
      • C#
      • Java
      • Ruby
  • Production Readiness
    • Availability
    • Observability & Support
    • Security
  • Integrations
    • Segment Integration
    • Clerk Integration
    • WorkOS Integration
    • Salesforce Integration
    • Hubspot Integration
  • Playbooks
    • Overview
    • Creating a metered feature
    • Backfills and usage corrections
    • Rolling out beta functionality with Flags
    • Handling customer exceptions and feature trials
    • Automatically provision customers using Stripe
    • Build a slack webhook
LogoLogo
SupportDashboard
On this page
  • Installation and Setup
  • Usage examples
  • Sending identify events
  • Sending track events
  • Creating and updating companies
  • Creating and updating users
  • Checking flags
  • Checking flags with entitlement details
  • Checking multiple flags
  • Other API operations
  • Testing
  • Offline Mode
  • Webhook Verification
  • Verifying Webhook Signatures
  • DataStream
  • Key Features:
  • How to Enable DataStream
  • Example:
  • Configuring Redis Cache
  • 1. Single Redis Instance
  • Example:
  • 2. Redis Cluster
  • Example:
  • Replicator Mode
  • How to Enable Replicator Mode
  • Cache TTL Configuration
  • Advanced Configuration (Optional)
  • Default Configuration
  • Errors
Developer ResourcesSDKs

Go

Was this page helpful?
Previous

Node.js

Next
Built with

Installation and Setup

  1. Install the Go library:
$go get github.com/schematichq/schematic-go
  1. Issue an API key for the appropriate environment using the Schematic app. Be sure to capture the secret key when you issue the API key; you’ll only see this key once, and this is what you’ll use with the Schematic Go library.

  2. Using this secret key, initialize a client in your Go application:

1import (
2 "os"
3
4 option "github.com/schematichq/schematic-go/option"
5 schematicclient "github.com/schematichq/schematic-go/client"
6)
7
8func main() {
9 apiKey := os.Getenv("SCHEMATIC_API_KEY")
10 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
11 defer client.Close()
12}

By default, the client will do some local caching for flag checks, if you would like to change this behavior, you can do so using an initialization option to specify the max size of the cache (in terms of number of records) and the max age of the cache (as a time.Duration):

1import (
2 "os"
3 "time"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7)
8
9func main() {
10 apiKey := os.Getenv("SCHEMATIC_API_KEY")
11 cacheSize := 100
12 cacheTTL := 1 * time.Millisecond
13 client := schematicclient.NewSchematicClient(
14 option.WithAPIKey(apiKey),
15 option.WithLocalFlagCheckCache(cacheSize, cacheTTL),
16 )
17 defer client.Close()
18}

You can also disable local caching entirely with an initialization option; bear in mind that, in this case, every flag check will result in a network request:

1import (
2 "os"
3
4 option "github.com/schematichq/schematic-go/option"
5 schematicclient "github.com/schematichq/schematic-go/client"
6)
7
8func main() {
9 apiKey := os.Getenv("SCHEMATIC_API_KEY")
10 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey), option.WithDisabledFlagCheckCache())
11 defer client.Close()
12}

You may want to specify default flag values for your application, which will be used if there is a service interruption or if the client is running in offline mode (see below). You can do this using an initialization option:

1import (
2 "os"
3
4 option "github.com/schematichq/schematic-go/option"
5 schematicclient "github.com/schematichq/schematic-go/client"
6)
7
8func main() {
9 apiKey := os.Getenv("SCHEMATIC_API_KEY")
10 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey), option.WithDefaultFlagValues(map[string]bool{
11 "some-flag-key": true,
12 }))
13 defer client.Close()
14}

Usage examples

A number of these examples use keys to identify companies and users. Learn more about keys here.

Sending identify events

Create or update users and companies using identify events.

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7 schematicgo "github.com/schematichq/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 client.Identify(context.Background(), &schematicgo.EventBodyIdentify{
16 Event: "some-action",
17 Company: map[string]string{
18 "id": "your-company-id",
19 },
20 User: map[string]string{
21 "email": "wcoyote@acme.net",
22 "user-id": "your-user-id",
23 },
24 Name: "Wile E. Coyote",
25 Traits: map[string]any{
26 "city": "Atlanta",
27 "login_count": 24,
28 "is_staff": false,
29 },
30 })
31}

This call is non-blocking and there is no response to check.

Sending track events

Track activity in your application using track events; these events can later be used to produce metrics for targeting.

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7 schematicgo "github.com/schematichq/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 client.Track(context.Background(), &schematicgo.EventBodyTrack{
16 Event: "some-action",
17 Company: map[string]string{
18 "id": "your-company-id",
19 },
20 User: map[string]string{
21 "email": "wcoyote@acme.net",
22 "user-id": "your-user-id",
23 },
24 })
25}

This call is non-blocking and there is no response to check.

If you want to record large numbers of the same event at once, or perhaps measure usage in terms of a unit like tokens or memory, you can optionally specify a quantity for your event:

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7 schematicgo "github.com/schematichq/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 client.Track(context.Background(), &schematicgo.EventBodyTrack{
16 Event: "query-tokens",
17 Company: map[string]string{
18 "id": "your-company-id",
19 },
20 User: map[string]string{
21 "email": "wcoyote@acme.net",
22 "user-id": "your-user-id",
23 },
24 Quantity: schematicgo.Int(1500),
25 })
26}

Creating and updating companies

Although it is faster to create companies and users via identify events, if you need to handle a response, you can use the companies API to upsert companies. Because you use your own identifiers to identify companies, rather than a Schematic company ID, creating and updating companies are both done via the same upsert operation:

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7 schematicgo "github.com/schematichq/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 body := &schematicgo.UpsertCompanyRequestBody{
16 Keys: map[string]string{
17 "id": "your-company-id",
18 },
19 Name: "Acme Widgets, Inc.",
20 Traits: map[string]any{
21 "city": "Atlanta",
22 "high_score": 25,
23 "is_active": true,
24 },
25 })
26 resp, err := client.API().Companies.UpsertCompany(context.Background, body)
27}

You can define any number of company keys; these are used to address the company in the future, for example by updating the company’s traits or checking a flag for the company. You can also define any number of company traits; these can then be used as targeting parameters.

Creating and updating users

Similarly, you can upsert users using the Schematic API, as an alternative to using identify events. Because you use your own identifiers to identify users, rather than a Schematic user ID, creating and updating users are both done via the same upsert operation:

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/SchematicHQ/schematic-go/client"
7 schematicgo "github.com/SchematicHQ/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 body := &schematicgo.UpsertUserRequestBody{
16 Keys: map[string]string{
17 "email": "wcoyote@acme.net",
18 "user-id": "your-user-id",
19 },
20 Company: map[string]string{
21 "id": "your-company-id",
22 },
23 Name: "Wile E. Coyote",
24 Traits: map[string]any{
25 "city": "Atlanta",
26 "login_count": 24,
27 "is_staff": false,
28 },
29 })
30
31 resp, err := client.API().Companies.UpsertUser(context.Background(), body)
32}

You can define any number of user keys; these are used to address the user in the future, for example by updating the user’s traits or checking a flag for the user. You can also define any number of user traits; these can then be used as targeting parameters.

Checking flags

When checking a flag, you’ll provide keys for a company and/or keys for a user. You can also provide no keys at all, in which case you’ll get the default value for the flag.

1import (
2 "context"
3 "os"
4
5 option "github.com/schematichq/schematic-go/option"
6 schematicclient "github.com/schematichq/schematic-go/client"
7 schematicgo "github.com/schematichq/schematic-go"
8)
9
10func main() {
11 apiKey := os.Getenv("SCHEMATIC_API_KEY")
12 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
13 defer client.Close()
14
15 evaluationCtx := schematicgo.CheckFlagRequestBody{
16 Company: map[string]string{
17 "id": "your-company-id",
18 },
19 User: map[string]string{
20 "email": "wcoyote@acme.net",
21 "user-id": "your-user-id",
22 },
23 }
24
25 if client.CheckFlag(context.Background(), "some-flag-key", evaluationCtx) {
26 // Flag is on
27 } else {
28 // Flag is off
29 }
30}

Checking flags with entitlement details

If you need more detail about how a flag check was resolved, including any entitlement associated with the check, use CheckFlagWithEntitlement. This returns a response object with the flag value, the reason for the evaluation result, and entitlement details such as usage, allocation, and credit balances when applicable.

1import (
2 "context"
3 "fmt"
4 "os"
5
6 option "github.com/schematichq/schematic-go/option"
7 schematicclient "github.com/schematichq/schematic-go/client"
8 schematicgo "github.com/schematichq/schematic-go"
9)
10
11func main() {
12 apiKey := os.Getenv("SCHEMATIC_API_KEY")
13 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
14 defer client.Close()
15
16 evaluationCtx := &schematicgo.CheckFlagRequestBody{
17 Company: map[string]string{
18 "id": "your-company-id",
19 },
20 User: map[string]string{
21 "email": "wcoyote@acme.net",
22 "user-id": "your-user-id",
23 },
24 }
25
26 resp, err := client.CheckFlagWithEntitlement(context.Background(), evaluationCtx, "some-flag-key")
27 if err != nil {
28 fmt.Printf("Error: %v\n", err)
29 return
30 }
31
32 fmt.Printf("Flag: %s, Value: %v, Reason: %s\n", resp.FlagKey, resp.Value, resp.Reason)
33
34 if resp.Entitlement != nil {
35 fmt.Printf("Entitlement type: %s\n", resp.Entitlement.ValueType)
36 if resp.Entitlement.Usage != nil && resp.Entitlement.Allocation != nil {
37 fmt.Printf("Usage: %d\n", *resp.Entitlement.Usage)
38 }
39 if resp.Entitlement.CreditRemaining != nil {
40 fmt.Printf("Credit remaining: %.2f\n", *resp.Entitlement.CreditRemaining)
41 }
42 }
43}

Checking multiple flags

When you need to evaluate several flags for the same company/user context, CheckFlags returns the full result for each requested flag in a single call. Pass nil or an empty slice for keys to retrieve every flag defined for the context.

1import (
2 "context"
3 "fmt"
4 "os"
5
6 option "github.com/schematichq/schematic-go/option"
7 schematicclient "github.com/schematichq/schematic-go/client"
8 schematicgo "github.com/schematichq/schematic-go"
9)
10
11func main() {
12 apiKey := os.Getenv("SCHEMATIC_API_KEY")
13 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
14 defer client.Close()
15
16 evaluationCtx := &schematicgo.CheckFlagRequestBody{
17 Company: map[string]string{
18 "id": "your-company-id",
19 },
20 }
21
22 results := client.CheckFlags(context.Background(), evaluationCtx, []string{"feature-flag-1", "feature-flag-2"})
23 for _, r := range results {
24 fmt.Printf("%s: %v (%s)\n", r.FlagKey, r.Value, r.Reason)
25 }
26}

Results are returned in the same order as the requested keys. When any requested key is a cache miss, CheckFlags refreshes the full set from the API so every returned value comes from a consistent evaluation. If DataStream is enabled and connected, keys are evaluated locally — any failure causes a transparent fallback to the API. In offline mode, each key resolves to its configured default.

Other API operations

The Schematic API supports many operations beyond these, accessible via client.API(). See the API submodule readme for a full list and documentation of supported operations.

Testing

Offline Mode

In development or testing environments, you may want to avoid making network requests to the Schematic API. You can run Schematic in offline mode by not providing an API key to the client:

1import (
2 schematicclient "github.com/schematichq/schematic-go/client"
3)
4
5func main() {
6 client := schematicclient.NewSchematicClient()
7 defer client.Close()
8}

You can also enable offline mode by providing an empty API key:

1import (
2 option "github.com/schematichq/schematic-go/option"
3 schematicclient "github.com/schematichq/schematic-go/client"
4)
5
6func main() {
7 client := schematicclient.NewSchematicClient(option.WithAPIKey(""))
8 defer client.Close()
9}

Or, by using the offline mode option:

1import (
2 option "github.com/schematichq/schematic-go/option"
3 schematicclient "github.com/schematichq/schematic-go/client"
4)
5
6func main() {
7 client := schematicclient.NewSchematicClient(option.WithOfflineMode())
8 defer client.Close()
9}

Offline mode works well with flag defaults:

1import (
2 option "github.com/schematichq/schematic-go/option"
3 schematicclient "github.com/schematichq/schematic-go/client"
4)
5
6func main() {
7 client := schematicclient.NewSchematicClient(option.WithOfflineMode(), option.WithDefaultFlagValues(map[string]bool{
8 "some-flag-key": true,
9 }))
10 defer client.Close()
11}

In an automated testing context, you may also want to use offline mode and specify single flag responses for test cases:

1import (
2 option "github.com/schematichq/schematic-go/option"
3 schematicclient "github.com/schematichq/schematic-go/client"
4)
5
6func TestSomeFunctionality(t *testing.T) {
7 client := schematicclient.NewSchematicClient(option.WithOfflineMode())
8 defer client.Close()
9
10 client.SetFlagDefault("some-flag-key", true)
11
12 // test code that expects the flag to be on
13}

Webhook Verification

Schematic can send webhooks to notify your application of events. To ensure the security of these webhooks, Schematic signs each request using HMAC-SHA256. The Go SDK provides utility functions to verify these signatures.

Verifying Webhook Signatures

When your application receives a webhook request from Schematic, you should verify its signature to ensure it’s authentic. The SDK provides a simple function to verify webhook signatures:

1import (
2 "net/http"
3
4 "github.com/schematichq/schematic-go/webhooks"
5)
6
7func yourWebhookHandler(w http.ResponseWriter, r *http.Request) {
8 // Each webhook has a distinct secret; you can access this via the Schematic app
9 webhookSecret := "your-webhook-secret"
10
11 // Verify the webhook signature
12 // The request body is preserved for later reading
13 err := webhooks.VerifyWebhookSignature(r, webhookSecret)
14 if err != nil {
15 // The signature is invalid or there was another error
16 http.Error(w, "Invalid signature", http.StatusBadRequest)
17 return
18 }
19
20 // The signature is valid, process the webhook...
21}

If you want to verify a webhook signature outside of the context of a web request, you can also do that using the webhooks.VerifySignature function.

DataStream

The DataStream functionality in Schematic provides real-time updates for flags, companies, and users. It uses WebSocket connections to receive updates from the Schematic backend and caches the data locally or in Redis for efficient access.

Key Features:

  • Real-Time Updates: Automatically updates cached data when changes occur on the backend.
  • Configurable Caching: Supports both in-memory (local) caching and Redis-based caching.
  • Efficient Flag Checks: Flag evaluation happens locally reducing number of network calls.

How to Enable DataStream

To enable the DataStream functionality, you need to pass the WithDatastream option when creating a new SchematicClient.

Example:

1import (
2 schematicclient "github.com/schematichq/schematic-go/client"
3 schematicoption "github.com/schematichq/schematic-go/option"
4)
5
6func main() {
7 apiKey := "your-api-key"
8
9 options := &core.DatastreamOptions{
10 CacheTTL: 5 * time.Minute, // Set the cache TTL (time-to-live)
11 }
12
13 client := schematicclient.NewSchematicClient(
14 schematicoption.WithAPIKey(apiKey),
15 schematicoption.WithDatastream(options),
16 )
17
18 defer client.Close()
19}

Configuring Redis Cache

The DataStream functionality supports Redis for caching company and user data. You can configure it to use either a single Redis instance or a Redis cluster.

1. Single Redis Instance

To use a single Redis instance, create a RedisCacheConfig and pass it to the DatastreamOptions.

Example:

1options := &schematicoption.DatastreamOptions{
2 CacheTTL: 5 * time.Minute, // Set the cache TTL
3 CacheConfig: &schematicoption.RedisCacheConfig{
4 Addr: "localhost:6379", // Redis server address
5 Password: "", // Redis password (if any)
6 DB: 0, // Redis database index
7 MaxRetries: 3, // Number of retries for failed operations
8 DialTimeout: 5 * time.Second, // Connection timeout
9 ReadTimeout: 3 * time.Second, // Read timeout
10 WriteTimeout: 3 * time.Second, // Write timeout
11 },
12}

2. Redis Cluster

To use a Redis cluster, create a RedisCacheClusterConfig and pass it to the DatastreamOptions.

Example:

1options := &schematicoption.DatastreamOptions{
2 CacheTTL: 5 * time.Minute, // Set the cache TTL
3 CacheConfig: &schematicoption.RedisCacheClusterConfig{
4 Addrs: []string{"localhost:7000", "localhost:7001", "localhost:7002"}, // Cluster node addresses
5 Password: "", // Redis password (if any)
6 MaxRetries: 3, // Number of retries for failed operations
7 DialTimeout: 5 * time.Second, // Connection timeout
8 ReadTimeout: 3 * time.Second, // Read timeout
9 WriteTimeout: 3 * time.Second, // Write timeout
10 RouteByLatency: true, // Route requests to the node with the lowest latency
11 RouteRandomly: false, // Disable random routing
12 },
13}

Replicator Mode

When running the schematic-datastream-replicator service, you should configure the Schematic client to operate in Replicator Mode. In this mode, the client connects to the external replicator service instead of establishing its own WebSocket connections, and the replicator handles all data streaming and caching automatically.

How to Enable Replicator Mode

To enable Replicator Mode, simply use the WithReplicatorMode() option within WithDatastream():

1import (
2 schematicclient "github.com/schematichq/schematic-go/client"
3 "github.com/schematichq/schematic-go/core"
4)
5
6func main() {
7 apiKey := "your-api-key"
8
9 client := schematicclient.NewSchematicClient(
10 core.WithAPIKey(apiKey),
11 core.WithDatastream(
12 core.WithReplicatorMode(),
13 ),
14 )
15
16 defer client.Close()
17}

Cache TTL Configuration

Important: When using Replicator Mode, you must set the SDK’s cache TTL to match the replicator’s cache TTL. The replicator defaults to an unlimited cache TTL (0). If the SDK uses a shorter TTL (the default is 24 hours), locally updated cache entries (e.g. after track events) will be written back with the shorter TTL and eventually evicted from the shared Redis cache, even though the replicator originally set them with no expiration.

To match the replicator’s default unlimited TTL:

1client := schematicclient.NewSchematicClient(
2 core.WithAPIKey(apiKey),
3 core.WithDatastream(
4 core.WithReplicatorMode(),
5 core.WithCacheTTL(0), // Unlimited, matching the replicator default
6 ),
7)

If you have configured a custom cache TTL on the replicator, use the same value here.

Advanced Configuration (Optional)

The client automatically configures sensible defaults for replicator mode, but you can customize the configuration if needed:

1client := schematicclient.NewSchematicClient(
2 core.WithAPIKey(apiKey),
3 core.WithDatastream(
4 core.WithReplicatorMode(),
5 core.WithCacheTTL(0), // Match the replicator's cache TTL
6 core.WithReplicatorHealthURL("http://my-replicator:8090/ready"),
7 core.WithReplicatorHealthInterval(60*time.Second),
8 ),
9)

Default Configuration

  • Replicator Health URL: http://localhost:8090/ready
  • Health Check Interval: 30 seconds
  • Cache TTL: 24 hours (SDK default; should be set to match the replicator’s TTL, which defaults to unlimited)

When running in Replicator Mode, the client will:

  • Skip establishing WebSocket connections
  • Periodically check if the replicator service is ready
  • Use cached data populated by the external replicator service
  • Fall back to direct API calls if the replicator is not available

Errors

Structured error types are returned from API calls that return non-success status codes. For example, you can check if the error was due to an unauthorized request (i.e. status code 401) with the following:

1import (
2 "context"
3 "os"
4
5 core "github.com/schematichq/schematic-go/core"
6 option "github.com/schematichq/schematic-go/option"
7 schematicclient "github.com/schematichq/schematic-go/client"
8 schematicgo "github.com/schematichq/schematic-go"
9)
10
11func main() {
12 apiKey := os.Getenv("SCHEMATIC_API_KEY")
13 client := schematicclient.NewSchematicClient(option.WithAPIKey(apiKey))
14 defer client.Close()
15
16 body := &schematicgo.UpsertCompanyRequestBody{
17 Keys: map[string]string{
18 "id": "your-company-id",
19 },
20 Name: "Acme Widgets, Inc.",
21 Traits: map[string]any{
22 "city": "Atlanta",
23 "high_score": 25,
24 "is_active": true,
25 },
26 })
27 resp, err := client.API().Companies.UpsertCompany(context.Background, body)
28 if err != nil {
29 if apiError, ok := err.(*core.APIError); ok {
30 switch (apiError.StatusCode) {
31 case http.StatusUnauthorized:
32 // Do something with the unauthorized request ...
33 }
34 }
35 return err
36 }
37}

These errors are also compatible with the errors.Is and errors.As APIs, so you can access the error like so:

1if err != nil {
2 var apiError *core.APIError
3 if errors.As(err, apiError) {
4 // Do something with the API error ...
5 }
6 return err
7}