Developer Documentation
Three SDKs. One interface per product. Auth, Platform, and Analytics.
Platform SDK
Install the SDK, implement one interface, and you have provisioning, activation, health, and admin endpoints. Available in four languages.
Getting Started
// Install: dotnet add package FF.Platform.Sdk
builder.AddFFPlatform<MyTenantStore>();
app.MapPlatformEndpoints();
// Provision, activate, health, admin - all handled.
// go get gitea.lab/fastflowtech/reseller/sdk/go-sdk
platform := ffplatform.New(opts, store, analyticsClient)
platform.RegisterRoutes(mux)
// Four endpoints, one interface.
# pip install ffplatform
from ffplatform.adapters.fastapi import register_platform_routes
register_platform_routes(app, MyStore(), PlatformOptions(...))
# Zero dependencies. FastAPI or stdlib.
// bun add ffplatform
import { registerPlatformRoutes } from "ffplatform";
registerPlatformRoutes(app, new MyStore(), { provisionApiKey: "..." });
// Hono adapter. Global fetch for callbacks.
SDKs
NuGet package. Two lines of setup, four endpoints, full provisioning lifecycle.
dotnet add package FF.Platform.Sdk
/v1/tenants/provision - Create tenant resources
/v1/tenants/activate - Activate a tenant
/v1/admin/databases - List all tenant databases
/health - Health check with SDK versions
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
builder.AddFFPlatform<MyTenantStore>();
app.MapPlatformEndpoints();
public interface ITenantStore
{
Task<object?> ProvisionAsync(
string orgSlug,
string? stripeCustomerId,
string? alkeyonOrgId);
Task<object?> ActivateAsync(
string orgSlug,
string? alkeyonOrgId);
Task<List<DatabaseInfo>> GetDatabasesAsync();
}
Go module. Stdlib net/http compatible. One struct, one interface.
go get gitea.lab/fastflowtech/reseller/sdk/go-sdk
/v1/tenants/provision - Create tenant resources
/v1/tenants/activate - Activate a tenant
/v1/admin/databases - List all tenant databases
/health - Health check with SDK versions
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
platform := ffplatform.New(opts, store, analytics)
platform.RegisterRoutes(mux)
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)
}
Zero dependencies. FastAPI adapter included. Sync or async - your choice.
pip install ffplatform \
--extra-index-url http://srv-119-gitea.lab:3000/api/packages/fastflowtech/pypi/simple
register_platform_routes(app, store, opts)
handle_platform_route(method, path, body, store, opts)
provision_api_key
Shared key for auth
product_name
Product name for analytics
version
Product version string
data_directory
Base directory for data
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",
))
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]: ...
Hono adapter. Global fetch for callbacks. Fully typed.
bun add ffplatform
From Gitea npm registry
/v1/tenants/provision - Create tenant resources
/v1/tenants/activate - Activate a tenant
/v1/admin/databases - List all tenant databases
/health - Health check with SDK versions
provisionApiKey
Shared key for auth
productName
Product name for analytics
version
Product version string
dataDirectory
Base directory for data
import { registerPlatformRoutes } from "ffplatform";
registerPlatformRoutes(app, store, {
provisionApiKey: "...",
productName: "my-product",
version: "1.0.0",
});
Hono peer dependency required
interface TenantStore {
provision(req: ProvisionRequest): Promise<Record<string, unknown> | null>;
activate(req: ActivateRequest): Promise<Record<string, unknown> | null>;
listDatabases(): Promise<DatabaseInfo[]>;
}
Endpoints
Creates tenant resources. Authenticated with X-Api-Key header. Fires callbacks asynchronously after success.
{
"orgSlug": "acme-corp",
"stripeCustomerId": "cus_...",
"alkeyonOrgId": "org_...",
"productCallbacks": [
"https://other-svc/v1/tenants/provision"
]
}
{
"orgSlug": "acme-corp",
"status": "provisioned"
}
Custom data from your TenantStore is merged into the response.
Marks a tenant as active. Called after setup is complete. Authenticated with X-Api-Key header.
{
"orgSlug": "acme-corp",
"alkeyonOrgId": "org_..."
}
{
"orgSlug": "acme-corp",
"status": "active"
}
Lists all tenant databases. Authenticated with X-Api-Key header. Used by the admin dashboard.
[
{
"orgSlug": "acme-corp",
"status": "active",
"dbPath": "/data/acme-corp/main.db",
"sizeBytes": 524288,
"createdAt": "2026-01-15T09:30:00Z"
}
]
No authentication required. Returns product info, SDK versions, and current status.
{
"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
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.
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;
}
}
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
}
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
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;
}
}
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.
POST https://your-service/v1/tenants/provision
Content-Type: application/json
X-Api-Key: shared-provision-key
{
"alkeyonOrgId": "org_...",
"slug": "acme-corp"
}
X-Api-Key header with the shared provision API key
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.
platform.provision
Fired after a successful provision. Includes orgSlug and product name.
platform.activate
Fired after a successful activation. Includes orgSlug and product name.
One interface, four languages. Your product handles tenant logic - the SDK handles everything else.