Skip to main content
Version: Latest

Platform Integrations

Flux integrates with multiple external platforms for market trading, data storage, and energy optimization.

Axle Energy

Overview

Axle Energy provides flexibility trading and grid services market access.

Integration Points

  • Dispatch Signals: Receive power setpoint commands
  • Availability Updates: Report battery availability
  • Settlement Data: Energy delivered reports

API Configuration

integrations:
axle:
api_url: "https://api.axle.energy/v1"
api_key: "${AXLE_API_KEY}"
site_id: "wlce-001"
update_interval: 60s

Dispatch Handling

// Fetch dispatch instructions
dispatch, err := axleClient.GetCurrentDispatch()
if err == nil && dispatch.Active {
targetPower = dispatch.PowerSetpoint
commandSource = AXLE_DISPATCH
}

Webhooks

{
"event": "dispatch_update",
"timestamp": "2024-01-01T12:00:00Z",
"site_id": "wlce-001",
"dispatch": {
"start_time": "2024-01-01T12:00:00Z",
"end_time": "2024-01-01T12:30:00Z",
"power_kw": 50.0,
"energy_kwh": 25.0
}
}

Modo Energy

Overview

Modo Energy provides market intelligence and Net Imbalance Volume (NIV) estimates.

NIV Data Access

type ModoClient struct {
apiKey string
baseURL string
}

func (m *ModoClient) GetNIVEstimate() (float64, error) {
resp, err := m.get("/niv/latest")
if err != nil {
return 0, err
}
return resp.NIV_MW * 1000, nil // Convert MW to kW
}

Configuration

integrations:
modo:
api_url: "https://api.modo.energy/v1"
api_key: "${MODO_API_KEY}"
refresh_interval: 60s

Data Structure

{
"timestamp": "2024-01-01T12:00:00Z",
"niv_mw": -150.5,
"confidence": 0.85,
"settlement_period": 24,
"forecast_horizon": "T+0"
}

Supabase

Overview

Cloud database platform for telemetry storage and API access.

Authentication

// Service role for backend operations
client := supabase.NewClient(
os.Getenv("SUPABASE_URL"),
os.Getenv("SUPABASE_SERVICE_KEY"),
)

// Anon key for frontend access
publicClient := supabase.NewClient(
os.Getenv("SUPABASE_URL"),
os.Getenv("SUPABASE_ANON_KEY"),
)

Data Upload

func uploadTelemetry(readings []BessReading) error {
// Batch insert with upsert
resp, err := client.
From("mg_bess_readings").
Insert(readings).
OnConflict("device_id,time").
Execute()

if err != nil {
return fmt.Errorf("upload failed: %w", err)
}
return nil
}

Real-time Subscriptions

// Subscribe to battery state changes
const channel = supabase
.channel('bess-updates')
.on('postgres_changes',
{
event: 'INSERT',
schema: 'flux',
table: 'mg_bess_readings',
filter: 'device_id=eq.uuid-here'
},
(payload) => {
console.log('New reading:', payload.new)
}
)
.subscribe()

RLS Policies

-- Allow read access to authenticated users
CREATE POLICY "Allow authenticated read" ON flux.mg_bess_readings
FOR SELECT
USING (auth.role() = 'authenticated');

-- Service role bypass
CREATE POLICY "Service role full access" ON flux.mg_bess_readings
USING (auth.jwt() ->> 'role' = 'service_role');

Environment Variables

Required Keys

# Supabase
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_ANON_KEY="your-anon-key-here"
export SUPABASE_SERVICE_KEY="your-service-key-here"

# Axle Energy
export AXLE_API_KEY="your-axle-api-key"
export AXLE_WEBHOOK_SECRET="your-webhook-secret"

# Modo Energy
export MODO_API_KEY="your-modo-api-key"

Security Best Practices

  • Store keys in secure environment files
  • Use service accounts, not personal keys
  • Rotate keys regularly
  • Monitor API usage
  • Implement rate limiting

Webhook Handling

Axle Dispatch Webhook

func handleAxleWebhook(w http.ResponseWriter, r *http.Request) {
// Verify signature
signature := r.Header.Get("X-Axle-Signature")
if !verifySignature(r.Body, signature) {
http.Error(w, "Invalid signature", 401)
return
}

// Process dispatch
var dispatch AxleDispatch
json.NewDecoder(r.Body).Decode(&dispatch)

// Update controller
controller.SetDispatch(dispatch)

w.WriteHeader(200)
}

Rate Limiting

API Limits

PlatformLimitWindow
Supabase1000 req1 minute
Axle100 req1 minute
Modo60 req1 minute

Handling Rate Limits

type RateLimitedClient struct {
client *http.Client
limiter *rate.Limiter
}

func (r *RateLimitedClient) Get(url string) (*http.Response, error) {
// Wait for token
err := r.limiter.Wait(context.Background())
if err != nil {
return nil, err
}

// Make request
resp, err := r.client.Get(url)

// Check for rate limit
if resp.StatusCode == 429 {
retryAfter := resp.Header.Get("Retry-After")
time.Sleep(parseRetryAfter(retryAfter))
return r.Get(url) // Retry
}

return resp, err
}

Error Handling

Circuit Breaker Pattern

type CircuitBreaker struct {
failures int
threshold int
timeout time.Duration
lastFail time.Time
}

func (cb *CircuitBreaker) Call(fn func() error) error {
if cb.isOpen() {
return errors.New("circuit open")
}

err := fn()
if err != nil {
cb.recordFailure()
return err
}

cb.reset()
return nil
}

Monitoring

Integration Health Checks

func checkIntegrations() map[string]bool {
status := map[string]bool{
"supabase": checkSupabase(),
"axle": checkAxle(),
"modo": checkModo(),
}
return status
}

Metrics Collection

metrics:
- name: integration_requests_total
type: counter
labels: [platform, status]

- name: integration_latency_seconds
type: histogram
labels: [platform, endpoint]

- name: integration_errors_total
type: counter
labels: [platform, error_type]

Next Steps