SDK Quick Start — bRRAIn Docs

Write your first Platform SDK program: build a client, write a vault file, read it back and list its folder, and test it locally against a fake pod.

SDK Quick Start

In this guide you write a small program that writes a note into your extension's vault namespace, reads it back and lists the folder, then test it on your own machine against a fake pod. About 15 minutes.

Prerequisites

  • A module set up as in Installation: developer-seat access, Go 1.25.9 or later, and the require plus replace lines in go.mod.
  • Basic Go: modules, context, net/http/httptest.

1. Write the program

Create cmd/hello-notes/main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	platformsdk "github.com/Qosil/bRRAIn/pkg/platform-sdk"
)

const slug = "hello-notes"

func mustEnv(name string) string {
	v := strings.TrimSpace(os.Getenv(name))
	if v == "" {
		log.Fatalf("%s: %s is required (the supervisor sets it)", slug, name)
	}
	return v
}

func main() {
	c, err := platformsdk.New(platformsdk.Options{
		BaseURL: mustEnv("BRRAIN_INTERNAL_URL"),
		Token:   mustEnv("BRRAIN_INTERNAL_TOKEN"),
		// AppSlug is left empty: New reads BRRAIN_APP_SLUG, which the
		// supervisor sets to your manifest's slug.
	})
	if err != nil {
		log.Fatalf("%s: build SDK client: %v", slug, err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	if err := run(ctx, c, time.Now().UTC()); err != nil {
		log.Fatalf("%s: %v", slug, err)
	}
}

// run writes a note into the extension's own namespace, reads it back,
// and lists the folder it lives in.
func run(ctx context.Context, c *platformsdk.Client, now time.Time) error {
	dir := "extensions/" + slug + "/notes"
	path := dir + "/" + now.Format("2006-01-02") + "-hello.md"
	note := fmt.Sprintf(`---
title: "Hello from %s"
date: %s
author: "[AI] %s"
tags: []
---

First note written through Platform SDK %s.
`, slug, now.Format("2006-01-02"), slug, platformsdk.SDKVersion)

	if err := c.Vault().Write(ctx, platformsdk.VaultWriteRequest{
		Path:    path,
		Content: []byte(note),
	}); err != nil {
		return fmt.Errorf("write %s: %w", path, err)
	}

	body, err := c.Vault().ReadBytes(ctx, path)
	if err != nil {
		return fmt.Errorf("read %s: %w", path, err)
	}
	fmt.Printf("read back %d bytes from %s\n", len(body), path)

	entries, err := c.Vault().List(ctx, dir)
	if err != nil {
		return fmt.Errorf("list %s: %w", dir, err)
	}
	for _, e := range entries {
		fmt.Printf("  %s (%d bytes)\n", e.Name, e.Size)
	}
	return nil
}

What to notice:

  • Configuration comes from the supervisor. BRRAIN_INTERNAL_URL and BRRAIN_INTERNAL_TOKEN are set for your process when the pod starts it. The program fails fast with a clear message if either is missing.
  • One client, many calls. c.Vault() is cheap; build the client once.
  • Your namespace. extensions/<slug>/ is private to your extension. Other extensions get 403 there.
  • A complete file. The note carries a title, a date, an honest [AI] author and a tags: property (empty here; see Vault paths and POPE tags).
  • ReadBytes, not Read. File content travels base64-encoded. ReadBytes returns the stored bytes; Read returns the raw wire value.

2. Test it without a pod

The SDK is plain HTTP, so you can serve the pod's wire format from httptest and run the real client against it. Create cmd/hello-notes/main_test.go:

package main

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"path"
	"strings"
	"testing"
	"time"

	platformsdk "github.com/Qosil/bRRAIn/pkg/platform-sdk"
)

// fakePod serves the three internal routes run() uses, in the same wire
// format as a brain pod: file content travels as base64 ([]byte in JSON).
func fakePod(t *testing.T) *httptest.Server {
	t.Helper()
	files := map[string][]byte{}
	mux := http.NewServeMux()
	auth := func(w http.ResponseWriter, r *http.Request) bool {
		if r.Header.Get("Authorization") != "Bearer test-token" {
			http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
			return false
		}
		return true
	}
	mux.HandleFunc("POST /internal/vault/file", func(w http.ResponseWriter, r *http.Request) {
		if !auth(w, r) {
			return
		}
		var req platformsdk.VaultWriteRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		files[req.Path] = req.Content
		w.Write([]byte(`{"status":"ok"}`))
	})
	mux.HandleFunc("GET /internal/vault/file", func(w http.ResponseWriter, r *http.Request) {
		if !auth(w, r) {
			return
		}
		p := r.URL.Query().Get("path")
		body, ok := files[p]
		if !ok {
			http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
			return
		}
		json.NewEncoder(w).Encode(map[string]any{"path": p, "content": body})
	})
	mux.HandleFunc("GET /internal/vault/list", func(w http.ResponseWriter, r *http.Request) {
		if !auth(w, r) {
			return
		}
		dir := r.URL.Query().Get("path")
		var entries []platformsdk.VaultEntry
		for p, b := range files {
			if path.Dir(p) == dir {
				entries = append(entries, platformsdk.VaultEntry{Path: p, Name: path.Base(p), Size: int64(len(b))})
			}
		}
		json.NewEncoder(w).Encode(platformsdk.VaultListResponse{Path: dir, Entries: entries})
	})
	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)
	return srv
}

func TestRun(t *testing.T) {
	srv := fakePod(t)
	c, err := platformsdk.New(platformsdk.Options{
		BaseURL: srv.URL,
		Token:   "test-token",
		AppSlug: "hello-notes", // explicit in tests; the supervisor sets it in production
	})
	if err != nil {
		t.Fatal(err)
	}
	now := time.Date(2026, 10, 1, 9, 0, 0, 0, time.UTC)
	if err := run(context.Background(), c, now); err != nil {
		t.Fatalf("run: %v", err)
	}
}

func TestRunWrongToken(t *testing.T) {
	srv := fakePod(t)
	c, _ := platformsdk.New(platformsdk.Options{BaseURL: srv.URL, Token: "wrong"})
	err := run(context.Background(), c, time.Now())
	if err == nil || !strings.Contains(err.Error(), "returned 401") {
		t.Fatalf("want a 401 APIError, got %v", err)
	}
}

Run it:

go test ./cmd/hello-notes -v

Both tests pass. The second one shows what a wrong token looks like: an *platformsdk.APIError whose message reads platformsdk: POST /internal/vault/file returned 401: ....

3. Run it on a pod

The internal API is served to extensions on the pod; it is not a public endpoint. To run against real data, package the program as an extension (a manifest.json naming your slug, binary, port variable and the sub-clients you call: here vault.read, vault.write, vault.list) and install it on a development pod. The supervisor then supplies BRRAIN_INTERNAL_URL, BRRAIN_INTERNAL_TOKEN and BRRAIN_APP_SLUG. A long-running extension serves HTTP; see Go with net/http for the server shape, and the course's packaging module for the manifest, install and update.

Troubleshooting

| Symptom | Meaning | | --- | --- | | platformsdk: BaseURL is required or BaseURL must be absolute | BRRAIN_INTERNAL_URL is empty or has no scheme | | APIError 401 | Missing or wrong token | | APIError 403 on a vault path | The path is in another extension's namespace or a platform-reserved one | | APIError 404 on read | The file does not exist yet; often a normal first-run branch | | A connection refused transport error | Nothing is listening at BRRAIN_INTERNAL_URL: the pod is not ready or the URL is wrong | | json: cannot unmarshal string into Go value | You passed Read's raw result to json.Unmarshal; use ReadBytes |

Next