Developer Documentation

FastFlowTech SDKs

Three SDKs. One interface per product. Auth, Platform, and Analytics.

Auth SDK

ALKEYON

Identity, JWT validation, OIDC, and org switching.

C# Go Python
View docs

Platform SDK

FF.Platform

Provisioning, activation, health, and admin endpoints.

C# Go Python TypeScript
View docs

Analytics SDK

FF.Analytics

Event tracking, batching, and query API.

C# Go
View docs

Platform SDK

Platform SDK Documentation

Install the SDK, implement one interface, and you have provisioning, activation, health, and admin endpoints. Available in four languages.

Getting Started

Quick Start

Program.cs - with FF.Platform.Sdk
// Install: dotnet add package FF.Platform.Sdk

builder.AddFFPlatform<MyTenantStore>();

app.MapPlatformEndpoints();

// Provision, activate, health, admin - all handled.
main.go - with ffplatform
// go get gitea.lab/fastflowtech/reseller/sdk/go-sdk

platform := ffplatform.New(opts, store, analyticsClient)
platform.RegisterRoutes(mux)

// Four endpoints, one interface.
app.py - with ffplatform
# pip install ffplatform

from ffplatform.adapters.fastapi import register_platform_routes

register_platform_routes(app, MyStore(), PlatformOptions(...))

# Zero dependencies. FastAPI or stdlib.
index.ts - with ffplatform
// bun add ffplatform

import { registerPlatformRoutes } from "ffplatform";

registerPlatformRoutes(app, new MyStore(), { provisionApiKey: "..." });

// Hono adapter. Global fetch for callbacks.

SDKs

FF.Platform.Sdk for C#

NuGet package. Two lines of setup, four endpoints, full provisioning lifecycle.

Install

dotnet add package FF.Platform.Sdk

What you get

  • POST /v1/tenants/provision - Create tenant resources
  • POST /v1/tenants/activate - Activate a tenant
  • GET /v1/admin/databases - List all tenant databases
  • GET /health - Health check with SDK versions

Configuration

FFPlatform:ProvisionApiKey Shared key for provision/activate/admin
FFPlatform:DataDirectory Base directory for tenant data files
FFAnalytics:ProductName Product name for analytics events
FFAnalytics:Version Product version string

Setup

builder.AddFFPlatform<MyTenantStore>();

app.MapPlatformEndpoints();
ITenantStore - the one interface you implement
public interface ITenantStore
{
    Task<object?> ProvisionAsync(
        string orgSlug,
        string? stripeCustomerId,
        string? alkeyonOrgId);

    Task<object?> ActivateAsync(
        string orgSlug,
        string? alkeyonOrgId);

    Task<List<DatabaseInfo>> GetDatabasesAsync();
}

ffplatform for Go

Go module. Stdlib net/http compatible. One struct, one interface.

Install

go get gitea.lab/fastflowtech/reseller/sdk/go-sdk

What you get

  • POST /v1/tenants/provision - Create tenant resources
  • POST /v1/tenants/activate - Activate a tenant
  • GET /v1/admin/databases - List all tenant databases
  • GET /health - Health check with SDK versions

Configuration

FFPLATFORM_PROVISION_API_KEY Shared key for auth
FFPLATFORM_DATA_DIR Base directory for data
FFANALYTICS_PRODUCT_NAME Product name for events
FFANALYTICS_VERSION Product version

Setup

platform := ffplatform.New(opts, store, analytics)
platform.RegisterRoutes(mux)
TenantStore - the one interface you implement
type TenantStore interface {
    Provision(ctx context.Context, req ProvisionRequest) (any, error)
    Activate(ctx context.Context, req ActivateRequest) (any, error)
    ListDatabases(ctx context.Context) ([]DatabaseInfo, error)
}

ffplatform for Python

Zero dependencies. FastAPI adapter included. Sync or async - your choice.

Install

pip install ffplatform \
  --extra-index-url http://srv-119-gitea.lab:3000/api/packages/fastflowtech/pypi/simple

Adapters

  • FastAPI register_platform_routes(app, store, opts)
  • stdlib handle_platform_route(method, path, body, store, opts)

Configuration

provision_api_key Shared key for auth
product_name Product name for analytics
version Product version string
data_directory Base directory for data

Setup (FastAPI)

from ffplatform.adapters.fastapi import register_platform_routes

register_platform_routes(app, MyStore(), PlatformOptions(
    provision_api_key="...",
    product_name="my-product",
    version="1.0.0",
))
TenantStore Protocol - sync or async
class TenantStore(Protocol):
    def provision(self, org_slug: str,
                  stripe_customer_id: str | None,
                  alkeyon_org_id: str | None) -> dict | None: ...

    def activate(self, org_slug: str,
                 alkeyon_org_id: str | None) -> dict | None: ...

    def list_databases(self) -> list[DatabaseInfo]: ...

ffplatform for TypeScript

Hono adapter. Global fetch for callbacks. Fully typed.

Install

bun add ffplatform

From Gitea npm registry

What you get

  • POST /v1/tenants/provision - Create tenant resources
  • POST /v1/tenants/activate - Activate a tenant
  • GET /v1/admin/databases - List all tenant databases
  • GET /health - Health check with SDK versions

Configuration

provisionApiKey Shared key for auth
productName Product name for analytics
version Product version string
dataDirectory Base directory for data

Setup

import { registerPlatformRoutes } from "ffplatform";

registerPlatformRoutes(app, store, {
    provisionApiKey: "...",
    productName: "my-product",
    version: "1.0.0",
});

Hono peer dependency required

TenantStore - all methods async
interface TenantStore {
    provision(req: ProvisionRequest): Promise<Record<string, unknown> | null>;
    activate(req: ActivateRequest): Promise<Record<string, unknown> | null>;
    listDatabases(): Promise<DatabaseInfo[]>;
}

Endpoints

POST /v1/tenants/provision

Creates tenant resources. Authenticated with X-Api-Key header. Fires callbacks asynchronously after success.

Request body

{
  "orgSlug": "acme-corp",
  "stripeCustomerId": "cus_...",
  "alkeyonOrgId": "org_...",
  "productCallbacks": [
    "https://other-svc/v1/tenants/provision"
  ]
}

Success response

{
  "orgSlug": "acme-corp",
  "status": "provisioned"
}

Custom data from your TenantStore is merged into the response.

POST /v1/tenants/activate

Marks a tenant as active. Called after setup is complete. Authenticated with X-Api-Key header.

Request body

{
  "orgSlug": "acme-corp",
  "alkeyonOrgId": "org_..."
}

Success response

{
  "orgSlug": "acme-corp",
  "status": "active"
}

GET /v1/admin/databases

Lists all tenant databases. Authenticated with X-Api-Key header. Used by the admin dashboard.

Response

[
  {
    "orgSlug": "acme-corp",
    "status": "active",
    "dbPath": "/data/acme-corp/main.db",
    "sizeBytes": 524288,
    "createdAt": "2026-01-15T09:30:00Z"
  }
]

GET /health

No authentication required. Returns product info, SDK versions, and current status.

Response

{
  "status": "ok",
  "timestamp": "2026-03-08T12:00:00Z",
  "product": "my-product",
  "version": "1.2.0",
  "environment": "production",
  "sdks": [
    { "name": "FF.Platform.Sdk", "version": "1.0.0" },
    { "name": "FF.Analytics.Sdk", "version": "1.0.0" }
  ]
}

Reference

TenantStore Interface

The only thing your product implements. Three methods - provision, activate, and list databases. The SDK handles everything else: routing, API key validation, callbacks, analytics, and health.

C# - ITenantStore
public class MyStore : ITenantStore
{
    public async Task<object?> ProvisionAsync(
        string orgSlug, string? stripeId,
        string? alkeyonOrgId)
    {
        // Create DB, seed data, etc.
        return new { dbPath = $"/data/{orgSlug}" };
    }

    public async Task<object?> ActivateAsync(
        string orgSlug, string? alkeyonOrgId)
    {
        // Mark tenant as active
        return null;
    }

    public async Task<List<DatabaseInfo>>
        GetDatabasesAsync()
    {
        // Return all tenant databases
        return databases;
    }
}
Go - TenantStore
type MyStore struct{}

func (s *MyStore) Provision(
    ctx context.Context,
    req ffplatform.ProvisionRequest,
) (any, error) {
    // Create DB, seed data, etc.
    return map[string]any{
        "dbPath": "/data/" + req.OrgSlug,
    }, nil
}

func (s *MyStore) Activate(
    ctx context.Context,
    req ffplatform.ActivateRequest,
) (any, error) {
    // Mark tenant as active
    return nil, nil
}

func (s *MyStore) ListDatabases(
    ctx context.Context,
) ([]ffplatform.DatabaseInfo, error) {
    // Return all tenant databases
    return databases, nil
}
Python - TenantStore
class MyStore:
    def provision(self, org_slug, stripe_id, alkeyon_org_id):
        # Create DB, seed data, etc.
        return {"db_path": f"/data/{org_slug}"}

    def activate(self, org_slug, alkeyon_org_id):
        # Mark tenant as active
        return None

    def list_databases(self):
        # Return all tenant databases
        return databases
TypeScript - TenantStore
class MyStore implements TenantStore {
    async provision(req: ProvisionRequest) {
        // Create DB, seed data, etc.
        return { dbPath: `/data/${req.orgSlug}` };
    }

    async activate(req: ActivateRequest) {
        // Mark tenant as active
        return null;
    }

    async listDatabases() {
        // Return all tenant databases
        return databases;
    }
}

Callbacks

After a successful provision, the SDK fires callbacks to any URLs provided in the productCallbacks array. Callbacks are fire-and-forget - they run asynchronously and do not block the provision response.

Callback payload

POST https://your-service/v1/tenants/provision
Content-Type: application/json
X-Api-Key: shared-provision-key

{
  "alkeyonOrgId": "org_...",
  "slug": "acme-corp"
}

Behavior

  • Fire-and-forget after successful provision
  • 5-second timeout per callback
  • Sends X-Api-Key header with the shared provision API key
  • Failures are logged but do not affect the provision response

Analytics

The SDK automatically tracks provisioning events via the FF Analytics SDK. This is optional - if no analytics client is configured, the SDK works without it.

Events tracked

platform.provision

Fired after a successful provision. Includes orgSlug and product name.

platform.activate

Fired after a successful activation. Includes orgSlug and product name.

Ready to integrate?

One interface, four languages. Your product handles tenant logic - the SDK handles everything else.