spectrum

package module
v0.0.0-...-3bfa466 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 11 Imported by: 0

README

gospectrum

A Go client for the Photon platform with feature parity across both planes:

  • the management plane — the Spectrum HTTP API (projects, webhooks, platforms, lines, users) plus webhook delivery verification and the Dashboard API, and
  • the runtime plane — the gRPC services behind the official spectrum-ts SDK, for sending messages, downloading attachments/media/files, and subscribing to live events on iMessage, WhatsApp Business, and Slack.

Note: there is no official Go SDK from Photon; this is an independent client built from the published OpenAPI specs and the MIT-licensed .proto contracts shipped in Photon's npm packages. The module path github.com/datacatcorp/gospectrum is a placeholder — update go.mod and the import paths when you pick the repo's home.

Packages

Package What it covers
gospectrum (spectrum) The full Spectrum management API: projects, profiles & avatars, billing, dedicated lines, platform toggles, users, webhook registration, voice/SIP, iMessage, WhatsApp Business (accounts + Meta message templates), Slack (app config, installations, setup), and runtime token minting.
gospectrum/webhook Verifying X-Spectrum-Signature headers (HMAC-SHA256, constant-time, replay-protected), decoding event payloads with the complete content union, and a plug-in http.Handler.
gospectrum/imessage The iMessage runtime (gRPC): send text/attachments/multipart/stickers/reactions/edits/unsends, create chats, typing indicators, groups, polls, locations, mini-app cards, live event streams, and streaming attachment upload/download. Parity with @photon-ai/advanced-imessage.
gospectrum/whatsapp The WhatsApp Business runtime (gRPC): send text/media/templates/interactive/reactions/locations/contacts, media upload/download, mark-read, live + missed event replay. Parity with @photon-ai/whatsapp-business.
gospectrum/slack The Slack runtime (gRPC): post text/Block Kit/reactions, mark-read, WhoAmI, file upload and resumable streaming download, live + missed event replay, per-team routing. Parity with @photon-ai/slack.
gospectrum/dashboard The Photon Dashboard API: project CRUD (including retrieving Spectrum credentials) and the RFC 8628 device-login flow the photon CLI uses.

The management-plane packages are stdlib-only; the runtime packages add google.golang.org/grpc + google.golang.org/protobuf. The raw generated stubs are exported (imessage/imessagepb, whatsapp/whatsapppb, slack/slackpb) so nothing in the protocol is out of reach — the typed helpers just cover the common flows.

Telegram needs no Photon-specific client: the official provider talks directly to the Telegram Bot API with your own bot token, so use any Go Telegram library. The Terminal provider is an in-process dev REPL with no wire protocol.

Install

go get github.com/datacatcorp/gospectrum

Management API

Credentials are HTTP Basic: username projectId, password projectSecret (from photon projects show or the dashboard). One client == one project.

import spectrum "github.com/datacatcorp/gospectrum"

client := spectrum.New(os.Getenv("PROJECT_ID"), os.Getenv("PROJECT_SECRET"))

// Register a webhook — the signing secret is returned exactly once.
wh, err := client.Webhooks.Register(ctx, "https://your-app.com/spectrum-webhook")
if err != nil {
    log.Fatal(err)
}
fmt.Println("save this now:", wh.SigningSecret)

// Create a shared user.
user, err := client.Users.Create(ctx, spectrum.CreateUserRequest{
    Type:        spectrum.UserShared,
    PhoneNumber: "+15551234567",
})

// Hand the user a deep link to start messaging.
fmt.Println(client.Users.RedirectURL(user.ID, "hi!"))

// List dedicated iMessage lines.
lines, err := client.Lines.List(ctx, &spectrum.ListLinesOptions{
    Platform: spectrum.PlatformIMessage,
})

// Set the project avatar (upload + commit in one call).
f, _ := os.Open("avatar.png")
avatarURL, err := client.Projects.UploadAvatar(ctx, "image/png", f)

Errors are typed; the HTTP status code is authoritative:

_, err := client.Users.Get(ctx, id)
var apiErr *spectrum.Error
if errors.As(err, &apiErr) && apiErr.NotFound() {
    // 404 — user is gone
}

Requests that fail with 408/429/5xx (or transport errors) are retried with exponential backoff and jitter — idempotent methods only, unless you opt in with spectrum.WithRetryAllMethods(). Tune with spectrum.WithMaxRetries(n). The API's default rate limit is 5 requests/second/project.

Fields that distinguish "omit" from "set to null" (e.g. clearing SIP credentials or a line's name) use spectrum.Nullable:

client.Voice.UpsertSIPInbound(ctx, spectrum.UpsertSIPInboundRequest{
    Username: spectrum.Null[string](), // explicit null — clears it
    Password: spectrum.Null[string](),
})

Receiving webhooks

Every inbound message for the project is POSTed to your registered URLs as signed JSON. webhook.Handler wires up verification (signature, replay window), payload decoding, and the response codes the delivery worker expects (2xx ack, 401 bad signature, 5xx → retry):

import "github.com/datacatcorp/gospectrum/webhook"

http.Handle("/spectrum-webhook", webhook.Handler(
    os.Getenv("SPECTRUM_SIGNING_SECRET"),
    func(ctx context.Context, d *webhook.Delivery) error {
        msg := d.Event.Message
        if msg == nil {
            return nil // unknown event type — ack and move on
        }
        switch msg.Content.Type {
        case webhook.ContentText:
            log.Printf("%s: %s", msg.Sender.ID, msg.Content.Text)
        case webhook.ContentAttachment:
            log.Printf("file %s (%s)", msg.Content.Attachment.Name, msg.Content.Attachment.MIMEType)
        case webhook.ContentReaction:
            log.Printf("%s on %q", msg.Content.Reaction.Emoji, msg.Content.Reaction.Target.ContentPreview)
        }
        return nil // 200 — returning an error responds 500 and the worker retries
    },
))

Deliveries are at-least-once: dedupe on d.Event.Message.ID (scope with d.WebhookID if separate services keep separate dedup tables). Acknowledge fast — the worker times out attempts after 30 seconds — and queue slow work.

For manual control (existing routers, middleware):

body, err := webhook.VerifyRequest(r, secret, 2<<20)
if err != nil { /* 400 or 401 */ }
event, err := webhook.ParseEvent(body)

webhook.Verify errors wrap ErrSignatureMismatch, ErrStaleTimestamp, ErrInvalidTimestamp, and ErrMissingHeader for errors.Is. The default replay tolerance is 5 minutes (webhook.WithTolerance to change).

Sending messages & downloading attachments (runtime)

The runtime clients speak the same gRPC protocol as the official SDK — TLS to *.spectrum.photon.codes, per-call bearer/metadata auth, x-idempotency-key on mutating RPCs, and automatic retries when the server marks a failure replayable (x-retryable trailer). ConnectCloud bootstraps everything from your project credentials and keeps the short-lived line tokens refreshed:

import (
    spectrum "github.com/datacatcorp/gospectrum"
    "github.com/datacatcorp/gospectrum/imessage"
)

mgmt := spectrum.New(projectID, projectSecret)
cloud, err := imessage.ConnectCloud(ctx, mgmt) // shared line or every dedicated line
defer cloud.Close()

line := cloud.Default()

// Send: resolve the recipient to a chat once, then message it.
chat, err := line.CreateDirectChat(ctx, "+15551234567")
msg, err := line.SendText(ctx, chat.GetGuid(), "Hello from Go")
msg, err = line.SendFile(ctx, chat.GetGuid(), "photo.jpg", jpegBytes)

// Download: stream an attachment id from a webhook delivery.
d, err := line.DownloadAttachment(ctx, attachmentID)
defer d.Close()
fmt.Println(d.Info.GetFileName(), d.Info.GetMimeType(), d.Info.GetTotalBytes())
data, err := io.ReadAll(d)

Replying to webhooks: a delivery's space.phone names the line it arrived on — route with cloud.Line(space.Phone) (the pooled line reports imessage.SharedPhone). The attachment content arm's id is what DownloadAttachment takes.

WhatsApp Business and Slack follow the same shape:

wa, err := whatsapp.ConnectCloud(ctx, mgmt)
id, err := wa.Default().SendText(ctx, "+15551234567", "hola")
id, err = wa.Default().SendMedia(ctx, "+15551234567", "image", "image/jpeg", "cat.jpg", jpeg, "caption")
_, bytes, err := wa.Default().ReadMedia(ctx, inboundMediaID)

sl, err := slack.ConnectCloud(ctx, mgmt)
team := sl.Client.Team("T0123456789")
ts, channel, err := team.SendText(ctx, "C0456", "hello")
file, bytes, err := team.ReadFile(ctx, "F789")   // resumable: DownloadFile(ctx, id, offset)

For low-level control (an explicit server address and token, like the TS createClient), use each package's Dial. Live inbound events are on the raw stream clients — e.g. line.MessagesStream.SubscribeMessageEvents, line.Events.CatchUpEvents for gap-free catch-up, wa.Default().MessagesStream.SubscribeEvents, team.SubscribeEvents.

The vendored .proto contracts live in proto/ (see proto/README.md for provenance and regeneration).

A runnable example lives in examples/testbot — the Go equivalent of npm create spectrum-project's iMessage echo bot.

Endpoint status (July 2026): the iMessage and WhatsApp Business production endpoints are live (verified). slack.spectrum.photon.codes — the default baked into Photon's own SDK — does not resolve publicly yet; pass Address in slack.Options/CloudOptions when Photon publishes it.

Dashboard API & device login

import "github.com/datacatcorp/gospectrum/dashboard"

dc := dashboard.New("") // no token yet
auth, err := dc.DeviceCode(ctx, "my-cli", "")
fmt.Println("visit", auth.VerificationURIComplete) // or show auth.UserCode

session, err := dc.Authorize(ctx, "my-cli", auth) // polls until approved
authed := dashboard.New(session.AuthToken)

projects, err := authed.ListProjects(ctx)
// Each project carries SpectrumProjectID/ProjectSecret — the Basic-auth
// pair for spectrum.New(...)

Notes

  • Base URL: https://spectrum.photon.codes (HTTPS only). Point at staging with spectrum.WithBaseURL(...).
  • Escape hatch: client.Do(ctx, method, path, query, body, &out) calls any endpoint with auth, envelope decoding, and retries applied.
  • Forward compatibility: unknown line platforms, webhook event types, content arms, and space fields decode without errors — check Raw/Extra for anything this library predates.

Development

go test ./...      # httptest-based; no network or credentials needed
go vet ./...

License

MIT — see LICENSE.

Documentation

Overview

Package spectrum is a Go client for the Photon Spectrum API (https://photon.codes/docs/api-reference/introduction) — the HTTP management plane for a Spectrum project's webhooks, platforms, lines, and users.

The API uses HTTP Basic auth: the username is your projectId and the password is your projectSecret. Credentials are scoped to a single project, so a Client is too:

client := spectrum.New(projectID, projectSecret)

project, err := client.Projects.Get(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Println(project.Name)

Functionality is grouped into services mirroring the API's resource groups: Projects, Billing, Lines, Platforms, Users, Webhooks, Voice, IMessage, WhatsApp, Slack, and Fusor.

Errors

Every non-2xx response is returned as an *Error carrying the HTTP status code and the server's message:

_, err := client.Users.Get(ctx, userID)
var apiErr *spectrum.Error
if errors.As(err, &apiErr) && apiErr.NotFound() {
	// handle 404
}

Retries

Requests that fail with 408, 429, or a 5xx status — as well as transport-level errors — are retried with exponential backoff and jitter. By default only idempotent methods (GET, PUT, DELETE) are retried; see WithMaxRetries and WithRetryAllMethods.

Receiving webhooks

The webhook subpackage verifies X-Spectrum-Signature headers and decodes event payloads. See package github.com/datacatcorp/gospectrum/webhook.

Sending messages and downloading attachments

The runtime (gRPC) subpackages send messages, transfer attachments and media, and subscribe to live events, bootstrapped from this package's credentials via their ConnectCloud functions. See packages github.com/datacatcorp/gospectrum/imessage, github.com/datacatcorp/gospectrum/whatsapp, and github.com/datacatcorp/gospectrum/slack.

Dashboard API

The dashboard subpackage covers the Photon Dashboard API (project CRUD and the RFC 8628 device-login flow used by the CLI). See package github.com/datacatcorp/gospectrum/dashboard.

Index

Constants

View Source
const DefaultBaseURL = "https://spectrum.photon.codes"

DefaultBaseURL is the production Spectrum API host. HTTPS only — the API rejects plaintext connections.

View Source
const Version = "0.1.0"

Version is the client library version, reported in the User-Agent.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddedLine

type AddedLine struct {
	Line    IMessageLine `json:"line"`
	Billing LineBilling  `json:"billing"`
}

AddedLine is the result of AddIMessage.

type AvatarUpload

type AvatarUpload struct {
	// UploadURL accepts a single HTTP PUT of the image bytes.
	UploadURL string `json:"uploadUrl"`
	// Key is passed to the corresponding commit call after uploading.
	Key string `json:"key"`
}

AvatarUpload is a presigned upload slot for an avatar image.

type BillingService

type BillingService service

BillingService reads the project's plan and async-billing sync state.

func (*BillingService) GetStatus

func (s *BillingService) GetStatus(ctx context.Context) (*BillingStatus, error)

GetStatus returns the project's async-billing sync state.

GET /projects/{projectId}/billing/status

func (*BillingService) GetSubscription

func (s *BillingService) GetSubscription(ctx context.Context) (*Subscription, error)

GetSubscription returns the current plan tier and subscription status.

GET /projects/{projectId}/billing/subscription

type BillingStatus

type BillingStatus struct {
	SyncStatus          BillingSyncStatus `json:"syncStatus"`
	LastSyncedAt        *string           `json:"lastSyncedAt"`
	Error               *string           `json:"error"`
	Quantity            float64           `json:"quantity"`
	LastProrationAmount *float64          `json:"lastProrationAmount"`
}

BillingStatus is the project's async-billing sync state. After adding or deleting a line, poll GetStatus until SyncStatus is no longer BillingSyncing to read the final proration.

type BillingSyncStatus

type BillingSyncStatus string

BillingSyncStatus is the state of the async billing sync that runs after line changes.

const (
	BillingInSync  BillingSyncStatus = "in_sync"
	BillingSyncing BillingSyncStatus = "syncing"
	BillingFailed  BillingSyncStatus = "failed"
)

type Client

type Client struct {

	// Services mirroring the API's resource groups.
	Projects  *ProjectsService
	Billing   *BillingService
	Lines     *LinesService
	Platforms *PlatformsService
	Users     *UsersService
	Webhooks  *WebhooksService
	Voice     *VoiceService
	IMessage  *IMessageService
	WhatsApp  *WhatsAppService
	Slack     *SlackService
	Fusor     *FusorService
	// contains filtered or unexported fields
}

Client is a Spectrum API client scoped to a single project. Create one with New. Its zero value is not usable.

A Client is safe for concurrent use by multiple goroutines.

func New

func New(projectID, projectSecret string, opts ...Option) *Client

New returns a Client authenticated as the given project.

Retrieve credentials with `photon projects show` or from the dashboard at https://app.photon.codes.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the API host the client talks to.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body, out any) error

Do performs an authenticated request against the API and decodes the envelope's `data` field into out (unless out is nil). path must start with "/" and query may be nil. A non-nil body is sent as JSON.

The typed services cover every documented endpoint; Do is the escape hatch for endpoints added to the API before they are added here.

func (*Client) ProjectID

func (c *Client) ProjectID() string

ProjectID returns the project id this client authenticates as.

type CreateTemplateRequest

type CreateTemplateRequest struct {
	Name                string                  `json:"name"`
	Language            string                  `json:"language"`
	Category            TemplateCategory        `json:"category"`
	Components          []TemplateComponent     `json:"components"`
	ParameterFormat     TemplateParameterFormat `json:"parameterFormat,omitempty"`
	AllowCategoryChange bool                    `json:"allowCategoryChange,omitempty"`
}

CreateTemplateRequest maps to Meta's POST /{waba_id}/message_templates payload. Components follow Meta's components reference and are forwarded unchanged.

type CreateUserRequest

type CreateUserRequest struct {
	Type UserType `json:"type"`
	// PhoneNumber is the user's own number in E.164 form.
	PhoneNumber string `json:"phoneNumber"`
	// AssignedPhoneNumber is required when Type is UserDedicated and
	// must be omitted for UserShared.
	AssignedPhoneNumber string  `json:"assignedPhoneNumber,omitempty"`
	FirstName           *string `json:"firstName,omitempty"`
	LastName            *string `json:"lastName,omitempty"`
	Email               *string `json:"email,omitempty"`
}

CreateUserRequest creates a user. Type is required.

For UserShared, the server assigns a phone number from the shared pool and enforces the project's maxSharedUsers. Re-creating with an existing active PhoneNumber returns that same user and updates its name/email from any values supplied (nil fields are left unchanged).

For UserDedicated, AssignedPhoneNumber must be one of the project's dedicated line numbers (pick one with Lines.Route).

type CreatedTemplate

type CreatedTemplate struct {
	ID       string           `json:"id"`
	Status   TemplateStatus   `json:"status"`
	Category TemplateCategory `json:"category"`
}

CreatedTemplate is Meta's acknowledgement of a new template. Newly created templates start in TemplatePending and require Meta approval before they can be sent.

type EditTemplateRequest

type EditTemplateRequest struct {
	Components            []TemplateComponent `json:"components,omitempty"`
	Category              TemplateCategory    `json:"category,omitempty"`
	MessageSendTTLSeconds int                 `json:"messageSendTtlSeconds,omitempty"`
}

EditTemplateRequest edits an existing template. Only Components, Category, and MessageSendTTLSeconds are editable — Meta forbids changing name and language. At least one field must be set (an empty body returns 400). Editing is subject to Meta's template lifecycle: APPROVED templates can edit components/category (category changes require re-approval), REJECTED templates can edit any field, PAUSED templates have limited edits.

type Error

type Error struct {
	// StatusCode is the HTTP status code of the response.
	StatusCode int
	// Message is the server's explanation, when the body carried one.
	Message string
	// Method and Path identify the request that failed.
	Method string
	Path   string
	// Body is the raw response body, for debugging.
	Body []byte
}

Error is the error type returned for any non-2xx API response.

The Spectrum API's documented status codes:

401  missing or invalid project credentials
404  resource not found or already deleted
409  conflict — e.g. a resource with the same key already exists
422  request body failed schema validation
429  rate limit exceeded (default 5 requests/second/project)
5xx  Spectrum-side error, safe to retry with backoff

func (*Error) Conflict

func (e *Error) Conflict() bool

Conflict reports whether the response was 409 — for example, a resource with the same key already exists.

func (*Error) Error

func (e *Error) Error() string

func (*Error) NotFound

func (e *Error) NotFound() bool

NotFound reports whether the response was 404 — resource not found or already deleted.

func (*Error) RateLimited

func (e *Error) RateLimited() bool

RateLimited reports whether the response was 429 — the project exceeded its request rate limit.

func (*Error) ServerError

func (e *Error) ServerError() bool

ServerError reports whether the response was a 5xx — a Spectrum-side failure that is safe to retry with backoff.

func (*Error) Unauthorized

func (e *Error) Unauthorized() bool

Unauthorized reports whether the response was 401 — missing or invalid project credentials.

func (*Error) Validation

func (e *Error) Validation() bool

Validation reports whether the response was 422 — the request body failed schema validation.

type FusorService

type FusorService service

FusorService issues tokens for the Photon Fusor service.

func (*FusorService) IssueToken

func (s *FusorService) IssueToken(ctx context.Context) (*Token, error)

IssueToken issues a short-lived LightAuth JWT bound to the Photon Fusor service (codes.photon.spectrum.fusor) and the requesting project. The token's subject is the project id; downstream Fusor services treat it as the project-scoped capability.

POST /projects/{projectId}/fusor/token

type IMessageInfo

type IMessageInfo struct {
	Type IMessageServiceType `json:"type"`
}

IMessageInfo is the project's iMessage provisioning summary.

type IMessageLine

type IMessageLine struct {
	ID          string             `json:"id"`
	PhoneNumber string             `json:"phoneNumber"`
	Profile     LineProfileSummary `json:"profile"`
	Status      LineStatus         `json:"status"`
	CreatedAt   string             `json:"createdAt"`
}

IMessageLine is a dedicated iMessage phone line.

type IMessagePlatform

type IMessagePlatform struct {
	Enabled bool `json:"enabled"`
	// AutoScale allocates additional dedicated lines automatically as
	// user counts grow.
	AutoScale bool `json:"autoScale"`
}

IMessagePlatform is the iMessage platform entry.

type IMessageService

type IMessageService service

IMessageService reads the project's iMessage provisioning and issues runtime tokens.

func (*IMessageService) Info

Info returns whether the project's iMessage service is shared or dedicated.

GET /projects/{projectId}/imessage/

func (*IMessageService) IssueTokens

func (s *IMessageService) IssueTokens(ctx context.Context) (*IMessageTokens, error)

IssueTokens issues iMessage LightAuth tokens for the project.

POST /projects/{projectId}/imessage/tokens

func (*IMessageService) SharedAvailability

func (s *IMessageService) SharedAvailability(ctx context.Context, phoneNumber string) (bool, error)

SharedAvailability checks whether a new shared iMessage number can be assigned to the given phone number (E.164) under this project. It mirrors the allocation rules used by Users.Create, including reuse of a soft-deleted user's previously assigned number within the same project.

GET /projects/{projectId}/imessage/shared/availability

type IMessageServiceType

type IMessageServiceType string

IMessageServiceType is how the project's iMessage capacity is provisioned.

const (
	// IMessageShared rides the pooled shared line (Free/Pro plans).
	IMessageShared IMessageServiceType = "shared"
	// IMessageDedicated uses lines dedicated to the project
	// (Business plan).
	IMessageDedicated IMessageServiceType = "dedicated"
)

type IMessageTokens

type IMessageTokens struct {
	Type IMessageServiceType `json:"type"`

	// Dedicated projects.
	Auth    map[string]string `json:"auth,omitempty"`
	Numbers map[string]string `json:"numbers,omitempty"`

	// Shared projects.
	Token string `json:"token,omitempty"`

	ExpiresIn int `json:"expiresIn"`
}

IMessageTokens is the result of IssueTokens. Type selects which fields are populated:

  • IMessageDedicated: Auth maps instance id → LightAuth token and Numbers maps instance id → phone number.
  • IMessageShared: Token is the single LightAuth token.

ExpiresIn is the TTL in seconds shared by every returned token.

type Line

type Line struct {
	Platform Platform
	IMessage *IMessageLine
	WhatsApp *WhatsAppLine
	Raw      json.RawMessage
}

Line is one entry from List. Exactly one of the platform-specific fields is non-nil, selected by Platform. Raw preserves the original JSON, including fields introduced after this library version.

func (*Line) UnmarshalJSON

func (l *Line) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler, dispatching on the entry's platform tag.

type LineAvatarUpload

type LineAvatarUpload struct {
	ProjectID string `json:"projectId"`
	LineID    string `json:"lineId"`
	UploadURL string `json:"uploadUrl"`
	Key       string `json:"key"`
}

LineAvatarUpload is a presigned upload slot bound to a line.

type LineBilling

type LineBilling struct {
	Quantity        *float64          `json:"quantity"`
	ProrationAmount *float64          `json:"prorationAmount"`
	SyncStatus      BillingSyncStatus `json:"syncStatus"`
}

LineBilling is the Stripe adjustment triggered by adding or removing a line. Quantity and ProrationAmount are nil while the async sync is still running — poll Billing.GetStatus until SyncStatus is no longer BillingSyncing.

type LineProfile

type LineProfile struct {
	ProjectID   string  `json:"projectId"`
	LineID      string  `json:"lineId"`
	PhoneNumber string  `json:"phoneNumber"`
	FirstName   *string `json:"firstName"`
	LastName    *string `json:"lastName"`
	AvatarURL   *string `json:"avatarUrl"`
}

LineProfile is the stored name and avatar for a dedicated iMessage line.

type LineProfileSummary

type LineProfileSummary struct {
	FirstName *string `json:"firstName"`
	LastName  *string `json:"lastName"`
	AvatarURL *string `json:"avatarUrl"`
}

LineProfileSummary is the profile embedded in an iMessage line.

type LineStatus

type LineStatus string

LineStatus is the availability of a dedicated iMessage line.

const (
	LineAvailable   LineStatus = "available"
	LineUnavailable LineStatus = "unavailable"
	LineUnknown     LineStatus = "unknown"
)

type LinesService

type LinesService service

LinesService manages the project's dedicated phone lines.

func (*LinesService) AddIMessage

func (s *LinesService) AddIMessage(ctx context.Context) (*AddedLine, error)

AddIMessage allocates a new dedicated iMessage phone number for the project and updates the Stripe subscription quantity with pro-rated billing. Business plan only. (iMessage only for now; WhatsApp Business onboarding flows through the Meta registration endpoints.)

POST /projects/{projectId}/lines/

func (*LinesService) CommitAvatar

func (s *LinesService) CommitAvatar(ctx context.Context, lineID, key string) (*LineProfile, error)

CommitAvatar verifies an uploaded image key bound to this line and updates its stored profile while preserving its name.

POST /projects/{projectId}/lines/{lineId}/profile/avatar/commit

func (*LinesService) CreateAvatarUpload

func (s *LinesService) CreateAvatarUpload(ctx context.Context, lineID, contentType string) (*LineAvatarUpload, error)

CreateAvatarUpload returns a presigned upload URL and a storage key bound to this project and dedicated iMessage line. Most callers can use UploadAvatar instead.

POST /projects/{projectId}/lines/{lineId}/profile/avatar/upload

func (*LinesService) Delete

func (s *LinesService) Delete(ctx context.Context, lineID string) (*LineBilling, error)

Delete deallocates a dedicated line by id. For iMessage lines this decrements the Stripe subscription quantity with pro-rated credit (Business plan only). WhatsApp Business lines are removed without a billing change and return nil billing.

DELETE /projects/{projectId}/lines/{lineId}

func (*LinesService) GetProfile

func (s *LinesService) GetProfile(ctx context.Context, lineID string) (*LineProfile, error)

GetProfile returns the stored name and avatar for a dedicated iMessage line.

GET /projects/{projectId}/lines/{lineId}/profile

func (*LinesService) List

func (s *LinesService) List(ctx context.Context, opts *ListLinesOptions) ([]Line, error)

List returns the dedicated phone lines the project owns across all platforms. On iMessage Free or Pro plans, lines are assigned per-user rather than dedicated to the project — for those, redirect users via Users.RedirectURL instead.

GET /projects/{projectId}/lines/

func (*LinesService) Route

func (s *LinesService) Route(ctx context.Context) (*RoutedLine, error)

Route returns the single best dedicated iMessage line to assign a new user to, load-balancing by active user count and recent growth. Returns a 404 *Error if the project owns no dedicated iMessage lines.

GET /projects/{projectId}/lines/route

func (*LinesService) UpdateProfile

func (s *LinesService) UpdateProfile(ctx context.Context, lineID string, req UpdateLineProfileRequest) (*LineProfile, error)

UpdateProfile merges firstName and/or lastName into this line's stored profile while preserving its other name and avatar.

PATCH /projects/{projectId}/lines/{lineId}/profile

func (*LinesService) UploadAvatar

func (s *LinesService) UploadAvatar(ctx context.Context, lineID, contentType string, image io.Reader) (*LineProfile, error)

UploadAvatar sets a line's avatar in one call: it requests a presigned upload slot, PUTs the image bytes, and commits the key. contentType is the image MIME type, e.g. "image/png".

type ListLinesOptions

type ListLinesOptions struct {
	// Platform limits results to a single platform.
	Platform Platform
}

ListLinesOptions filters List.

type ListTemplatesOptions

type ListTemplatesOptions struct {
	Limit         int
	Name          string
	NameOrContent string
	Status        string
	Language      string
	Category      string
	// After and Before are opaque Meta paging cursors from a previous
	// page's TemplatePaging.
	After  string
	Before string
}

ListTemplatesOptions filters ListTemplates. Limit is required by the API; the other fields are forwarded to Meta Graph as filters.

type ListUsersOptions

type ListUsersOptions struct {
	// Type filters to shared or dedicated users.
	Type UserType
	// IDs batch-fetches specific users by id.
	IDs []string
	// Search is a partial, case-insensitive match on first/last name,
	// phone number, and email.
	Search string
	// Limit caps the page size (max 500). Zero returns all matches.
	Limit int
	// Offset skips past earlier matches when paging.
	Offset int
}

ListUsersOptions filters and pages List. Pagination is opt-in: leave Limit and Offset zero to return all matches.

type Nullable

type Nullable[T any] struct {
	// contains filtered or unexported fields
}

Nullable distinguishes the three states a PATCH field can be in: omitted from the request (leave unchanged), explicit JSON null (clear the stored value), or a concrete value (set it).

The zero value is "omitted". Struct fields of this type must carry the `omitzero` JSON tag so unset fields stay off the wire:

type req struct {
	Username Nullable[string] `json:"username,omitzero"`
}

Build values with NullableOf and Null:

spectrum.NullableOf("alice") // "username":"alice"
spectrum.Null[string]()      // "username":null
spectrum.Nullable[string]{}  // field omitted

func Null

func Null[T any]() Nullable[T]

Null returns a Nullable that serializes as JSON null.

func NullableOf

func NullableOf[T any](v T) Nullable[T]

NullableOf returns a Nullable holding v.

func (Nullable[T]) IsNull

func (n Nullable[T]) IsNull() bool

IsNull reports whether the field is an explicit JSON null.

func (Nullable[T]) IsZero

func (n Nullable[T]) IsZero() bool

IsZero reports whether the field is unset, which makes `omitzero` drop it during marshaling.

func (Nullable[T]) MarshalJSON

func (n Nullable[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*Nullable[T]) UnmarshalJSON

func (n *Nullable[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (Nullable[T]) Value

func (n Nullable[T]) Value() (T, bool)

Value returns the held value and whether one is present (set and not null).

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(base string) Option

WithBaseURL overrides the API host, e.g. for a staging backend.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the underlying *http.Client. Use it to configure timeouts, proxies, or transport middleware.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a failed request is retried on 408/429/5xx responses and transport errors. The default is 2. Pass 0 to disable retries.

func WithRetryAllMethods

func WithRetryAllMethods() Option

WithRetryAllMethods extends retries to POST and PATCH requests. By default only idempotent methods (GET, PUT, DELETE) are retried, since a retried POST that raced a timeout can be applied twice.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent overrides the User-Agent header.

type Platform

type Platform string

Platform identifies a messaging platform.

const (
	PlatformIMessage         Platform = "imessage"
	PlatformWhatsAppBusiness Platform = "whatsapp_business"
	PlatformVoice            Platform = "voice"
	PlatformSlack            Platform = "slack"
)

type Platforms

type Platforms struct {
	IMessage         *IMessagePlatform `json:"imessage"`
	WhatsAppBusiness *SimplePlatform   `json:"whatsapp_business"`
	Voice            *VoicePlatform    `json:"voice"`
	Slack            *SimplePlatform   `json:"slack"`
}

Platforms is the project's platform configuration, including disabled entries (metadata is preserved across toggles). Entries are nil when the platform has never been configured.

type PlatformsService

type PlatformsService service

PlatformsService toggles platforms on and off for the project and updates platform-specific metadata.

func (*PlatformsService) Get

Get returns the project's platform configuration.

GET /projects/{projectId}/platforms/

func (*PlatformsService) SetIMessageAutoScale

func (s *PlatformsService) SetIMessageAutoScale(ctx context.Context, autoScale bool) (*Platforms, error)

SetIMessageAutoScale updates the iMessage platform's autoScale flag.

func (*PlatformsService) SetVoiceIMessageEnabled

func (s *PlatformsService) SetVoiceIMessageEnabled(ctx context.Context, enabled bool) (*Platforms, error)

SetVoiceIMessageEnabled updates the voice platform's imessage_enabled flag.

func (*PlatformsService) Toggle

func (s *PlatformsService) Toggle(ctx context.Context, platform Platform, enabled bool) (*Platforms, error)

Toggle enables or disables a platform for the project. Any previously stored metadata is preserved across toggles.

PATCH /projects/{projectId}/platforms/

func (*PlatformsService) UpdateMetadata

func (s *PlatformsService) UpdateMetadata(ctx context.Context, platform Platform, metadata any) (*Platforms, error)

UpdateMetadata updates platform-specific metadata. It can only be called after the platform is enabled (409 otherwise); use Toggle to change `enabled`. metadata is the platform's metadata object — see the typed helpers SetIMessageAutoScale and SetVoiceIMessageEnabled for the two documented fields.

PATCH /projects/{projectId}/platforms/{platform}

type ProfileSyncError

type ProfileSyncError struct {
	LineID string `json:"lineId"`
	Reason string `json:"reason"`
}

ProfileSyncError describes a line that failed to sync.

type ProfileSyncResult

type ProfileSyncResult struct {
	ProjectID string `json:"projectId"`
	// TargetedLineCount is how many dedicated iMessage lines the sync
	// targets.
	TargetedLineCount int `json:"targetedLineCount"`
}

ProfileSyncResult is the acknowledgement returned by SyncProfile.

type ProfileSyncState

type ProfileSyncState string

ProfileSyncState is the aggregate state of a profile sync.

const (
	ProfileSyncInProgress    ProfileSyncState = "in_progress"
	ProfileSyncCompleted     ProfileSyncState = "completed"
	ProfileSyncPartialFailed ProfileSyncState = "partial_failed"
	ProfileSyncFailed        ProfileSyncState = "failed"
)

type ProfileSyncStatus

type ProfileSyncStatus struct {
	ProjectID string             `json:"projectId"`
	Status    ProfileSyncState   `json:"status"`
	Total     int                `json:"total"`
	Pending   int                `json:"pending"`
	Synced    int                `json:"synced"`
	Failed    int                `json:"failed"`
	Errors    []ProfileSyncError `json:"errors"`
}

ProfileSyncStatus reports per-line convergence of the project profile across dedicated iMessage lines.

type Project

type Project struct {
	Name string `json:"name"`
	Slug string `json:"slug"`
	// Profile is nil when the project has no profile set.
	Profile *ProjectProfileSummary `json:"profile"`
}

Project is the project summary returned by Get.

type ProjectProfile

type ProjectProfile struct {
	ProjectID string  `json:"projectId"`
	FirstName string  `json:"firstName"`
	LastName  string  `json:"lastName"`
	AvatarURL *string `json:"avatarUrl"`
}

ProjectProfile is the standalone profile resource.

type ProjectProfileSummary

type ProjectProfileSummary struct {
	FirstName string  `json:"firstName"`
	LastName  string  `json:"lastName"`
	AvatarURL *string `json:"avatarUrl"`
	// IMessageSynced is true iff the project is Business-tier and every
	// active dedicated iMessage line has the current profile applied.
	// Free/Pro projects ride a shared line and always report false.
	IMessageSynced bool `json:"imessageSynced"`
}

ProjectProfileSummary is the profile embedded in a Project.

type ProjectsService

type ProjectsService service

ProjectsService covers the project itself: display name, slug, profile (name + avatar shown to end users), and profile→line sync.

func (*ProjectsService) CommitAvatar

func (s *ProjectsService) CommitAvatar(ctx context.Context, key string) (string, error)

CommitAvatar validates a previously uploaded image and conditionally propagates the complete project profile. It returns the public avatar URL.

POST /projects/{projectId}/profile/avatar/commit

func (*ProjectsService) CreateAvatarUpload

func (s *ProjectsService) CreateAvatarUpload(ctx context.Context, contentType string) (*AvatarUpload, error)

CreateAvatarUpload returns a presigned PUT URL and the project-bound key to commit after uploading the avatar. Most callers can use UploadAvatar instead, which performs all three steps.

POST /projects/{projectId}/profile/avatar/upload

func (*ProjectsService) Get

func (s *ProjectsService) Get(ctx context.Context) (*Project, error)

Get fetches the project's display name, slug, and profile.

GET /projects/{projectId}/

func (*ProjectsService) GetProfile

func (s *ProjectsService) GetProfile(ctx context.Context) (*ProjectProfile, error)

GetProfile fetches the project profile.

GET /projects/{projectId}/profile

func (*ProjectsService) GetProfileSyncStatus

func (s *ProjectsService) GetProfileSyncStatus(ctx context.Context) (*ProfileSyncStatus, error)

GetProfileSyncStatus returns current aggregate line convergence.

GET /projects/{projectId}/profile/sync

func (*ProjectsService) SyncProfile

func (s *ProjectsService) SyncProfile(ctx context.Context) (*ProfileSyncResult, error)

SyncProfile idempotently aligns every active dedicated iMessage line profile to the project profile.

POST /projects/{projectId}/profile/sync

func (*ProjectsService) UpdateProfile

UpdateProfile updates the supplied project name fields and conditionally propagates the complete profile to lines that still match the old project profile.

PATCH /projects/{projectId}/profile

func (*ProjectsService) UpdateSlug

func (s *ProjectsService) UpdateSlug(ctx context.Context, slug string) (*Slug, error)

UpdateSlug replaces the project's slug. Slugs are 1–10 characters of lowercase letters, digits, and hyphens, with no leading or trailing hyphen (`^[a-z0-9](?:[a-z0-9-]{0,8}[a-z0-9])?$`). Invalid formats return 422; a slug owned by another active project returns 409.

PATCH /projects/{projectId}/slug/

func (*ProjectsService) UploadAvatar

func (s *ProjectsService) UploadAvatar(ctx context.Context, contentType string, image io.Reader) (string, error)

UploadAvatar sets the project avatar in one call: it requests a presigned upload slot, PUTs the image bytes, and commits the key. contentType is the image MIME type, e.g. "image/png". It returns the public avatar URL.

type RegisteredWebhook

type RegisteredWebhook struct {
	Webhook
	SigningSecret string `json:"signingSecret"`
}

RegisteredWebhook is the result of Register. SigningSecret (64 lowercase hex characters) is returned only here and can never be retrieved again — store it in your secrets manager immediately. If you lose it, delete the webhook and register the URL again.

type RoutedLine

type RoutedLine struct {
	Line IMessageLine `json:"line"`
	// IsBestAvailable is false when the returned line is the
	// least-bad fallback — it holds more than 500 users or grew by
	// more than 10 users in the last minute, meaning no genuinely
	// healthy line was available.
	IsBestAvailable bool `json:"isBestAvailable"`
}

RoutedLine is the result of Route.

type SIPInboundConfig

type SIPInboundConfig struct {
	ConfigID    string  `json:"configId"`
	ProjectID   string  `json:"projectId"`
	SIPURI      string  `json:"sipUri"`
	Username    *string `json:"username"`
	HasPassword bool    `json:"hasPassword"`
	CreatedAt   string  `json:"createdAt"`
	UpdatedAt   string  `json:"updatedAt"`
}

SIPInboundConfig is the project's SIP inbound configuration. The password is never returned; HasPassword indicates whether one is set.

type SimplePlatform

type SimplePlatform struct {
	Enabled bool `json:"enabled"`
}

SimplePlatform is a platform entry with no extra metadata.

type SlackAppConfig

type SlackAppConfig struct {
	AppConfigID       string   `json:"appConfigId"`
	ProjectID         string   `json:"projectId"`
	EnabledFeatures   []string `json:"enabledFeatures"`
	ClientID          *string  `json:"clientId"`
	ClientSecret      *string  `json:"clientSecret"`
	SigningSecret     *string  `json:"signingSecret"`
	AppID             *string  `json:"appId"`
	InstallationCount int      `json:"installationCount"`
	CreatedAt         string   `json:"createdAt"`
	UpdatedAt         string   `json:"updatedAt"`
}

SlackAppConfig is the project's Slack app configuration.

The GET and PUT endpoints return plaintext credentials (ClientSecret, SigningSecret) — only call them from trusted environments.

type SlackInstallation

type SlackInstallation struct {
	InstallationID    string   `json:"installationId"`
	AppConfigID       string   `json:"appConfigId"`
	ProjectID         string   `json:"projectId"`
	TeamID            string   `json:"teamId"`
	TeamName          string   `json:"teamName"`
	AppID             string   `json:"appId"`
	BotToken          string   `json:"botToken"`
	BotRefreshToken   *string  `json:"botRefreshToken"`
	BotTokenExpiresAt *string  `json:"botTokenExpiresAt"`
	BotUserID         string   `json:"botUserId"`
	GrantedScopes     []string `json:"grantedScopes"`
	InstalledAt       string   `json:"installedAt"`
	UpdatedAt         string   `json:"updatedAt"`
}

SlackInstallation is one workspace installation of the project's Slack app.

The list and upsert endpoints return plaintext bot tokens — only call them from trusted environments.

type SlackService

type SlackService service

SlackService manages the project's Slack app configuration and workspace installations and issues runtime tokens.

func (*SlackService) DeleteAppConfig

func (s *SlackService) DeleteAppConfig(ctx context.Context) error

DeleteAppConfig soft-deletes the project's active Slack app configuration. Existing installations are not deleted — they keep referencing the soft-deleted config until the project is deleted or the installations are explicitly removed.

DELETE /projects/{projectId}/slack/

func (*SlackService) DeleteInstallation

func (s *SlackService) DeleteInstallation(ctx context.Context, teamID string) error

DeleteInstallation soft-deletes a workspace installation, freeing the (app, team) slot so the same workspace can re-install via OAuth.

DELETE /projects/{projectId}/slack/installations/{teamId}

func (*SlackService) GetAppConfig

func (s *SlackService) GetAppConfig(ctx context.Context) (*SlackAppConfig, error)

GetAppConfig returns the project's active Slack app configuration plus the count of active installations. Returns a 404 *Error if no active config exists.

GET /projects/{projectId}/slack/

func (*SlackService) IssueTokens

func (s *SlackService) IssueTokens(ctx context.Context) (*SlackTokens, error)

IssueTokens issues per-installation Slack LightAuth tokens for the project.

POST /projects/{projectId}/slack/tokens

func (*SlackService) ListInstallations

func (s *SlackService) ListInstallations(ctx context.Context) ([]SlackInstallation, error)

ListInstallations returns every active installation owned by the project's active Slack app config.

GET /projects/{projectId}/slack/installations

func (*SlackService) Setup

Setup creates (or looks up) the project's Slack app by forwarding to Slack's app-manifest API. Use this when the dashboard OAuth proxy flow isn't available.

POST /projects/{projectId}/slack/setup

func (*SlackService) UpsertAppConfig

UpsertAppConfig creates or updates the project's Slack app configuration.

PUT /projects/{projectId}/slack/

func (*SlackService) UpsertInstallation

func (s *SlackService) UpsertInstallation(ctx context.Context, teamID string, req UpsertSlackInstallationRequest) (*SlackInstallation, error)

UpsertInstallation creates or updates an installation row for a workspace. Returns a 409 *Error if the project has no active Slack app config — call UpsertAppConfig (or Setup) first.

PUT /projects/{projectId}/slack/installations/{teamId}

type SlackSetupRequest

type SlackSetupRequest struct {
	AppName         string   `json:"appName"`
	EnabledFeatures []string `json:"enabledFeatures"`
	ConfigToken     string   `json:"configToken,omitempty"`
	RefreshToken    string   `json:"refreshToken,omitempty"`
}

SlackSetupRequest creates (or looks up) the project's Slack app via a Slack workspace-admin config token (xoxe.xoxp-…), required on first install. RefreshToken is the optional paired token (xoxe-…); when supplied it is persisted so the app manifest can be auto-updated later.

type SlackSetupResult

type SlackSetupResult struct {
	OK    bool    `json:"ok"`
	AppID *string `json:"appId"`
}

SlackSetupResult is the acknowledgement of Setup. AppID is nil when Slack did not return one.

type SlackTeam

type SlackTeam struct {
	TeamName      string   `json:"teamName"`
	BotUserID     string   `json:"botUserId"`
	AppID         string   `json:"appId"`
	GrantedScopes []string `json:"grantedScopes"`
}

SlackTeam describes one installed workspace in SlackTokens.

type SlackTokens

type SlackTokens struct {
	Auth      map[string]string    `json:"auth"`
	Teams     map[string]SlackTeam `json:"teams"`
	ExpiresIn int                  `json:"expiresIn"`
}

SlackTokens is the result of IssueTokens. Auth maps Slack team_id → LightAuth token and Teams maps team_id → workspace details; the map covers every workspace this deployment can act on. All tokens share the same TTL in seconds.

type Slug

type Slug struct {
	ProjectID   string `json:"projectId"`
	ProjectSlug string `json:"projectSlug"`
}

Slug is the result of UpdateSlug.

type Subscription

type Subscription struct {
	Tier string `json:"tier"`
	// Status is nil when the project has no subscription.
	Status            *SubscriptionStatus `json:"status"`
	CancelAtPeriodEnd bool                `json:"cancel_at_period_end"`
	SubscriptionID    *string             `json:"subscription_id"`
	CustomerID        *string             `json:"customer_id"`
}

Subscription is the project's current plan tier and subscription state.

type SubscriptionStatus

type SubscriptionStatus string

SubscriptionStatus is a Stripe-side subscription state.

const (
	SubscriptionActive   SubscriptionStatus = "active"
	SubscriptionCanceled SubscriptionStatus = "canceled"
	SubscriptionPastDue  SubscriptionStatus = "past_due"
)

type Template

type Template struct {
	ID              string                  `json:"id"`
	Name            string                  `json:"name"`
	Status          TemplateStatus          `json:"status"`
	Category        TemplateCategory        `json:"category"`
	Language        string                  `json:"language"`
	Components      []TemplateComponent     `json:"components"`
	ParameterFormat TemplateParameterFormat `json:"parameterFormat,omitempty"`
	QualityScore    *TemplateQualityScore   `json:"qualityScore,omitempty"`
	RejectedReason  string                  `json:"rejectedReason,omitempty"`
}

Template is a WhatsApp Business message template.

type TemplateCategory

type TemplateCategory string

TemplateCategory is a Meta message-template category.

const (
	TemplateMarketing      TemplateCategory = "MARKETING"
	TemplateUtility        TemplateCategory = "UTILITY"
	TemplateAuthentication TemplateCategory = "AUTHENTICATION"
)

type TemplateComponent

type TemplateComponent map[string]any

TemplateComponent is one entry of a template's `components` array in Meta's snake_case shape, forwarded unchanged. It must contain at least a "type" key. See Meta's components reference: https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates

type TemplateList

type TemplateList struct {
	Templates []Template     `json:"templates"`
	Paging    TemplatePaging `json:"paging"`
}

TemplateList is one page of templates.

type TemplatePaging

type TemplatePaging struct {
	NextCursor *string `json:"nextCursor"`
	PrevCursor *string `json:"prevCursor"`
}

TemplatePaging carries Meta's opaque paging cursors.

type TemplateParameterFormat

type TemplateParameterFormat string

TemplateParameterFormat is how a template's variables are addressed.

const (
	TemplatePositional TemplateParameterFormat = "POSITIONAL"
	TemplateNamed      TemplateParameterFormat = "NAMED"
)

type TemplateQualityScore

type TemplateQualityScore struct {
	Score string `json:"score"`
}

TemplateQualityScore is Meta's quality assessment of a template.

type TemplateStatus

type TemplateStatus string

TemplateStatus is a Meta message-template lifecycle state.

const (
	TemplateApproved        TemplateStatus = "APPROVED"
	TemplatePending         TemplateStatus = "PENDING"
	TemplateRejected        TemplateStatus = "REJECTED"
	TemplatePaused          TemplateStatus = "PAUSED"
	TemplateInAppeal        TemplateStatus = "IN_APPEAL"
	TemplatePendingDeletion TemplateStatus = "PENDING_DELETION"
	TemplateDeleted         TemplateStatus = "DELETED"
	TemplateDisabled        TemplateStatus = "DISABLED"
	TemplateLimitExceeded   TemplateStatus = "LIMIT_EXCEEDED"
)

type Token

type Token struct {
	Token     string `json:"token"`
	ExpiresIn int    `json:"expiresIn"`
}

Token is a short-lived LightAuth token. ExpiresIn is the TTL in seconds.

type UpdateLineProfileRequest

type UpdateLineProfileRequest struct {
	FirstName Nullable[string] `json:"firstName,omitzero"`
	LastName  Nullable[string] `json:"lastName,omitzero"`
}

UpdateLineProfileRequest merges name fields into a line's stored profile. Unset fields are preserved; explicit nulls clear them.

type UpdateProfileRequest

type UpdateProfileRequest struct {
	FirstName *string `json:"firstName,omitempty"`
	LastName  *string `json:"lastName,omitempty"`
}

UpdateProfileRequest carries the name fields to update. Nil fields are left unchanged.

type UpsertSIPInboundRequest

type UpsertSIPInboundRequest struct {
	SIPURI   string           `json:"sipUri,omitempty"`
	Username Nullable[string] `json:"username,omitzero"`
	Password Nullable[string] `json:"password,omitzero"`
}

UpsertSIPInboundRequest creates or patches the SIP inbound config.

On first call (no active config), SIPURI is required and the project's voice platform must be enabled. On subsequent calls any non-empty subset patches the existing config: unset fields are preserved, and explicit nulls (spectrum.Null[string]()) clear Username/Password. The resulting state must have Username and Password either both set or both null — half-credentials are rejected.

type UpsertSlackAppConfigRequest

type UpsertSlackAppConfigRequest struct {
	EnabledFeatures []string `json:"enabledFeatures,omitempty"`
	ClientID        string   `json:"clientId,omitempty"`
	ClientSecret    string   `json:"clientSecret,omitempty"`
	SigningSecret   string   `json:"signingSecret,omitempty"`
	AppID           string   `json:"appId,omitempty"`
}

UpsertSlackAppConfigRequest creates or updates the Slack app configuration. The update is partial: zero-valued fields are preserved. EnabledFeatures is validated against the server's feature catalog.

Never pass a Slack config token (xoxe.xoxp-…) here — that's a workspace-admin credential and must not be persisted; use Setup for the config-token flow instead.

type UpsertSlackInstallationRequest

type UpsertSlackInstallationRequest struct {
	TeamName             string   `json:"teamName"`
	AppID                string   `json:"appId"`
	BotToken             string   `json:"botToken"`
	BotRefreshToken      string   `json:"botRefreshToken,omitempty"`
	BotTokenExpiresInSec int      `json:"botTokenExpiresInSec,omitempty"`
	BotUserID            string   `json:"botUserId"`
	GrantedScopes        []string `json:"grantedScopes"`
}

UpsertSlackInstallationRequest creates or updates an installation row for a workspace, typically after Slack's oauth.v2.access succeeds.

type User

type User struct {
	ID        string   `json:"id"`
	ProjectID string   `json:"projectId"`
	Type      UserType `json:"type"`
	FirstName *string  `json:"firstName"`
	LastName  *string  `json:"lastName"`
	Email     *string  `json:"email"`
	// PhoneNumber is the user's own number (E.164).
	PhoneNumber string `json:"phoneNumber"`
	// AssignedPhoneNumber is the project line the user messages
	// (E.164).
	AssignedPhoneNumber string         `json:"assignedPhoneNumber"`
	Meta                map[string]any `json:"meta"`
	CreatedAt           string         `json:"createdAt"`
}

User is a project user.

type UserList

type UserList struct {
	Users []User `json:"users"`
	Total int    `json:"total"`
}

UserList is one page of users. Total counts all matches ignoring pagination.

type UserType

type UserType string

UserType selects the line-allocation model for a user.

const (
	// UserShared assigns the user a phone number from the shared
	// Cosmos pool.
	UserShared UserType = "shared"
	// UserDedicated pins the user to one of the project's dedicated
	// lines.
	UserDedicated UserType = "dedicated"
)

type UsersService

type UsersService service

UsersService manages the project's users — the people your agent talks to.

func (*UsersService) Create

func (s *UsersService) Create(ctx context.Context, req CreateUserRequest) (*User, error)

Create creates (or, for an existing active shared phone number, updates) a user.

POST /projects/{projectId}/users/

func (*UsersService) Delete

func (s *UsersService) Delete(ctx context.Context, userID string) error

Delete soft-deletes a user. The user no longer appears in listings; a shared user's assigned number can be reused if they are re-created later.

DELETE /projects/{projectId}/users/{userId}/

func (*UsersService) Get

func (s *UsersService) Get(ctx context.Context, userID string) (*User, error)

Get returns a single user by id.

GET /projects/{projectId}/users/{userId}/

func (*UsersService) List

func (s *UsersService) List(ctx context.Context, opts *ListUsersOptions) (*UserList, error)

List returns active users for the project.

GET /projects/{projectId}/users/

func (*UsersService) RedirectURL

func (s *UsersService) RedirectURL(userID, msg string) string

RedirectURL returns the public URL that redirects a shared user to the appropriate messaging platform (currently iMessage via an sms: deep link). msg optionally overrides the default message body; pass "" to use the default. Hand this URL to the end user — it requires no authentication.

GET /users/{userId}/redirect

func (*UsersService) ResolveRedirect

func (s *UsersService) ResolveRedirect(ctx context.Context, userID, msg string) (string, error)

ResolveRedirect calls the public redirect endpoint without following it and returns the platform deep link it points to (for example an sms: URL). Errors mirror the endpoint's documented responses: 403 when the user is not shared or the platform is disabled, 404 when the user is unknown, 422 when the user has no assigned number.

type VoicePlatform

type VoicePlatform struct {
	Enabled bool `json:"enabled"`
	// IMessageEnabled controls whether voice is reachable from
	// iMessage.
	IMessageEnabled bool `json:"imessage_enabled"`
}

VoicePlatform is the voice platform entry.

type VoiceService

type VoiceService service

VoiceService manages the project's SIP inbound configuration and issues voice runtime tokens.

func (*VoiceService) DeleteSIPInbound

func (s *VoiceService) DeleteSIPInbound(ctx context.Context) error

DeleteSIPInbound soft-deletes the project's active SIP inbound configuration. Returns a 404 *Error if no active config exists. After deletion a fresh config can be created via UpsertSIPInbound.

DELETE /projects/{projectId}/voice/sip-inbound/

func (*VoiceService) GetSIPInbound

func (s *VoiceService) GetSIPInbound(ctx context.Context) (*SIPInboundConfig, error)

GetSIPInbound returns the project's active SIP inbound configuration, or nil if none is configured.

GET /projects/{projectId}/voice/sip-inbound/

func (*VoiceService) IssueToken

func (s *VoiceService) IssueToken(ctx context.Context) (*Token, error)

IssueToken issues a single voice LightAuth token for the project. One token is returned regardless of dedicated/shared provisioning.

POST /projects/{projectId}/voice/tokens

func (*VoiceService) UpsertSIPInbound

func (s *VoiceService) UpsertSIPInbound(ctx context.Context, req UpsertSIPInboundRequest) (*SIPInboundConfig, error)

UpsertSIPInbound sets or updates the project's SIP inbound configuration.

PATCH /projects/{projectId}/voice/sip-inbound/

type Webhook

type Webhook struct {
	ID         string    `json:"id"`
	WebhookURL string    `json:"webhookUrl"`
	CreatedAt  time.Time `json:"createdAt"`
	UpdatedAt  time.Time `json:"updatedAt"`
}

Webhook is a registered delivery destination.

type WebhooksService

type WebhooksService service

WebhooksService registers, lists, and deletes the project's webhook destinations. To verify and decode deliveries arriving at those destinations, use package github.com/datacatcorp/gospectrum/webhook.

func (*WebhooksService) Delete

func (s *WebhooksService) Delete(ctx context.Context, webhookID string) error

Delete stops delivery to a webhook. Once this call returns, no further events are sent to its URL (a delivery already in flight may still complete), and the webhook's signing secret is invalidated.

To rotate a signing secret without dropping deliveries: Register the same URL again (obtaining a new id and secret), deploy the new secret, then Delete the old webhook id.

DELETE /projects/{projectId}/webhooks/{webhookId}

func (*WebhooksService) List

func (s *WebhooksService) List(ctx context.Context) ([]Webhook, error)

List returns the webhooks currently registered for the project, ordered by creation time (oldest first). Signing secrets are never included in list responses.

GET /projects/{projectId}/webhooks/

func (*WebhooksService) Register

func (s *WebhooksService) Register(ctx context.Context, webhookURL string) (*RegisteredWebhook, error)

Register adds a destination URL for the project. The URL must be a public HTTPS endpoint — the delivery worker won't POST to plain http://, private/internal addresses, or through redirects.

Every inbound message for the project is then delivered to the URL as a signed JSON POST; each registered URL receives every event independently.

POST /projects/{projectId}/webhooks/

type WhatsAppAccount

type WhatsAppAccount struct {
	AccountID    string  `json:"accountId"`
	WABAID       string  `json:"wabaId"`
	BusinessName *string `json:"businessName"`
	CreatedAt    string  `json:"createdAt"`
}

WhatsAppAccount is an onboarded WhatsApp Business account (WABA). AccountID scopes the template endpoints; WABAID is Meta's id.

type WhatsAppLine

type WhatsAppLine struct {
	State              WhatsAppLineState `json:"state"`
	ID                 string            `json:"id,omitempty"`
	PhoneNumberID      string            `json:"phoneNumberId"`
	DisplayPhoneNumber *string           `json:"displayPhoneNumber"`
	CreatedAt          string            `json:"createdAt"`

	// Registered-only fields.
	VerifiedName           *string `json:"verifiedName,omitempty"`
	QualityRating          *string `json:"qualityRating,omitempty"`
	Status                 *string `json:"status,omitempty"`
	CodeVerificationStatus *string `json:"codeVerificationStatus,omitempty"`
	WABAID                 string  `json:"wabaId,omitempty"`
	BusinessName           *string `json:"businessName,omitempty"`

	// Pending-only fields.
	RegistrationState WhatsAppRegistrationState `json:"registrationState,omitempty"`
	ErrorCode         *string                   `json:"errorCode,omitempty"`
	ErrorMessage      *string                   `json:"errorMessage,omitempty"`
}

WhatsAppLine is a WhatsApp Business line. State selects which fields are populated: a registered line carries ID, WABAID, and the quality fields; a pending one carries RegistrationState and the error fields.

type WhatsAppLineState

type WhatsAppLineState string

WhatsAppLineState distinguishes registered lines from ones still moving through Meta registration.

const (
	WhatsAppLineRegistered WhatsAppLineState = "registered"
	WhatsAppLinePending    WhatsAppLineState = "pending"
)

type WhatsAppRegistrationState

type WhatsAppRegistrationState string

WhatsAppRegistrationState is the progress of a pending WhatsApp Business line registration.

const (
	WhatsAppRegistering        WhatsAppRegistrationState = "registering"
	WhatsAppRegistrationFailed WhatsAppRegistrationState = "failed"
)

type WhatsAppService

type WhatsAppService service

WhatsAppService manages WhatsApp Business accounts and message templates and issues runtime tokens.

func (*WhatsAppService) CreateTemplate

func (s *WhatsAppService) CreateTemplate(ctx context.Context, accountID string, req CreateTemplateRequest) (*CreatedTemplate, error)

CreateTemplate creates a message template under the given WhatsApp Business account.

POST /projects/{projectId}/whatsapp-business/accounts/{accountId}/templates/

func (*WhatsAppService) DeleteTemplate

func (s *WhatsAppService) DeleteTemplate(ctx context.Context, accountID, templateID, name string) error

DeleteTemplate deletes a single language version of a message template. templateID is forwarded to Meta as hsm_id so the delete is scoped to that exact template row; name is required because Meta's delete endpoint requires it alongside hsm_id. Other language versions sharing the same name are untouched.

DELETE /projects/{projectId}/whatsapp-business/accounts/{accountId}/templates/{templateId}

func (*WhatsAppService) EditTemplate

func (s *WhatsAppService) EditTemplate(ctx context.Context, accountID, templateID string, req EditTemplateRequest) error

EditTemplate edits an existing message template.

PATCH /projects/{projectId}/whatsapp-business/accounts/{accountId}/templates/{templateId}

func (*WhatsAppService) IssueTokens

func (s *WhatsAppService) IssueTokens(ctx context.Context) (*WhatsAppTokens, error)

IssueTokens issues per-line WhatsApp Business LightAuth tokens for the project.

POST /projects/{projectId}/whatsapp-business/tokens

func (*WhatsAppService) ListAccounts

func (s *WhatsAppService) ListAccounts(ctx context.Context) ([]WhatsAppAccount, error)

ListAccounts lists the project's onboarded WhatsApp Business accounts, newest first. Returns an empty slice if the project has none.

GET /projects/{projectId}/whatsapp-business/accounts

func (*WhatsAppService) ListTemplates

func (s *WhatsAppService) ListTemplates(ctx context.Context, accountID string, opts ListTemplatesOptions) (*TemplateList, error)

ListTemplates lists message templates for a WhatsApp Business account. Component JSON is preserved in Meta's snake_case shape.

GET /projects/{projectId}/whatsapp-business/accounts/{accountId}/templates/

type WhatsAppTokens

type WhatsAppTokens struct {
	Auth      map[string]string  `json:"auth"`
	Numbers   map[string]*string `json:"numbers"`
	ExpiresIn int                `json:"expiresIn"`
}

WhatsAppTokens is the result of IssueTokens. Auth maps Meta phone_number_id → LightAuth token; Numbers maps phone_number_id → display phone number (nil when Meta has none on file). All tokens share the same TTL in seconds.

Directories

Path Synopsis
Package dashboard is a client for the Photon Dashboard API — the authenticated REST API behind app.photon.codes, used by the web app and the photon CLI for project CRUD and device login.
Package dashboard is a client for the Photon Dashboard API — the authenticated REST API behind app.photon.codes, used by the web app and the photon CLI for project CRUD and device login.
examples
testbot command
Command testbot is the Go equivalent of the app scaffolded by `npm create spectrum-project -- --providers imessage`: an iMessage echo bot on the gospectrum library.
Command testbot is the Go equivalent of the app scaffolded by `npm create spectrum-project -- --providers imessage`: an iMessage echo bot on the gospectrum library.
Package imessage is a Go client for Photon's iMessage runtime — the gRPC surface behind the official @photon-ai/advanced-imessage SDK.
Package imessage is a Go client for Photon's iMessage runtime — the gRPC surface behind the official @photon-ai/advanced-imessage SDK.
internal
grpcx
Package grpcx holds the transport behaviour shared by the Photon runtime gRPC clients (iMessage, WhatsApp Business, Slack): channel defaults, bearer/metadata auth, idempotency keys, and the x-retryable retry contract.
Package grpcx holds the transport behaviour shared by the Photon runtime gRPC clients (iMessage, WhatsApp Business, Slack): channel defaults, bearer/metadata auth, idempotency keys, and the x-retryable retry contract.
tokencache
Package tokencache caches short-lived runtime tokens minted by the Spectrum management API, refreshing them before expiry.
Package tokencache caches short-lived runtime tokens minted by the Spectrum management API, refreshing them before expiry.
Package slack is a Go client for Photon's Slack runtime — the gRPC surface behind the official @photon-ai/slack SDK.
Package slack is a Go client for Photon's Slack runtime — the gRPC surface behind the official @photon-ai/slack SDK.
Package webhook verifies and decodes Spectrum webhook deliveries.
Package webhook verifies and decodes Spectrum webhook deliveries.
Package whatsapp is a Go client for Photon's WhatsApp Business runtime — the gRPC surface behind the official @photon-ai/whatsapp-business SDK.
Package whatsapp is a Go client for Photon's WhatsApp Business runtime — the gRPC surface behind the official @photon-ai/whatsapp-business SDK.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL