Documentation
Getting Started

Introduction

🎉 API v2.0 & Flutter SDK v2.0 Released (May 2026)

Breaking Changes: Authentication now uses multi-tenant user isolation. Each app has its own user base.
• API: Registration requires app_uuid in request body
• API: Endpoints changed: /register, /login, /me, /logout (no /auth prefix)
• Flutter SDK: Firestack.initialize() now requires appId parameter
• Flutter SDK: signUp() simplified (no passwordConfirmation, name is optional)
• User model now includes project_id, app_uuid, custom_claims, and metadata

FireStack is a self-hosted, open-source Backend as a Service built on Laravel. It provides all the services mobile and web apps typically need — authentication, realtime database, file storage, real-time messaging, push notifications, remote config, and analytics — exposed as a clean REST API.

Base URL

https://your-domain.com/api/v1

All API requests require an X-API-Key header. Authenticated routes also require Authorization: Bearer <token>.

What's Included

Authentication & Tokens
Collections & Documents
Subcollections
File Storage & Signed URLs
Real-Time Messaging
Push Notifications
Remote Config & Feature Flags
Analytics & Events
Project Management
App Management
API Key Lifecycle
User Management
Filament Admin Panel
Webhook Support
Audit & Activity Logging
Broadcasting (Reverb)

Authentication Model

Every request uses a two-layer auth model:

LayerHeaderPurpose
1 — App IdentityX-API-KeyIdentifies which app & project the request belongs to
2 — User IdentityAuthorization: BearerIdentifies the authenticated user (Sanctum token)

Response Format

Standard success response
{
  "success": true,
  "data": { ... },
  "message": "Operation successful"
}
Standard error response
{
  "success": false,
  "message": "Validation failed",
  "errors": { "email": ["The email field is required."] },
  "error_code": "VALIDATION_ERROR",
  "timestamp": "2026-04-18T12:00:00Z"
}
Getting Started

Installation

1 Clone the repository
git clone https://github.com/your-org/firestack.git
cd firestack
2 Install dependencies
composer install
npm install
3 Configure environment
cp .env.example .env
php artisan key:generate
4 Run migrations & seed
php artisan migrate --seed
5 Build frontend assets
npm run build
6 Start the server
php artisan serve
# Visit http://localhost:8000

Default Credentials

UserEmailPassword
Adminadmin@firestack.testpassword
Test Usertest@example.compassword

Optional Services

WebSockets (real-time)
php artisan reverb:start
Queue worker (notifications, webhooks, file processing)
php artisan queue:work --queue=default,notifications,webhooks
Getting Started

Authentication Setup

FireStack uses a two-layer authentication model. Every request requires an API Key that identifies your app. User-scoped endpoints additionally require a Bearer token obtained by registering or logging in a user.

Two-Layer Auth Model

Request Headers
# Layer 1 — always required
X-API-Key: fsk_live_xxxxxxxxxxxxxxxxxxxx

# Layer 2 — required for user-scoped routes
Authorization: Bearer sanctum_token_here

Quick-Start Flow

1 Get an API key
2 Register a user
3 Authenticate requests

Route Protection Summary

Route GroupX-API-KeyBearer TokenRate Limit
POST /registerRequiredNot needed5/min
POST /loginRequiredNot needed5/min
POST /auth/forgot-passwordRequiredNot needed5/min
POST /auth/reset-passwordRequiredNot needed5/min
POST /files (upload)RequiredRequired10/min
POST /eventsRequiredRequired100/min
POST /events/batchRequiredRequired20/min
All other /api/v1 routesRequiredRequired60/min
Getting Started

API Keys

Every request to FireStack must include an X-API-Key header. API keys are scoped to an App, which belongs to a Project. Your key determines which project's data is accessible.

How to Get Your API Key

1 Create a Project

Sign in to the dashboard and click New Project. This is the top-level container for all your data.

2 Create an App

Inside your project, open the Apps tab and click New App. Choose a platform (Android, iOS, Web, Flutter).

3 Copy Your Credentials

An amber notice will show your API Key and API Secret exactly once. Copy them immediately.

4 Regenerate If Needed

Open the App detail page and click Generate New Key to invalidate the old one and receive a new key.

Using Your Key

HTTP Headers
X-API-Key: fsk_live_xxxxxxxxxxxxxxxxxxxx
Authorization: Bearer your_user_token_here   # required for authenticated routes

Key Properties

PropertyDetails
Prefixfsk_ followed by a random string
Shown oncePlain-text key is displayed once at creation; stored as SHA-256 hash
ScopeScoped to one App — different apps have different keys
EnvironmentKeys can be scoped to production, staging, or development
ExpirationOptional — keys can have an expiration date
RevocationGenerating a new key immediately invalidates the previous one

Security tip: Never expose your API key in client-side JavaScript or commit it to version control. Use environment variables or a secrets manager.

Authentication

Register

POST /api/v1/register

Register a new app user and receive an API token. Only requires the X-API-Key header (no Bearer token needed). Each app has its own isolated user base.

🔐 Multi-Tenant Auth (v3.0): Users are scoped to your app_uuid. The same email can register in different projects independently.

Request Body

FieldTypeRequiredDescription
app_uuidstring (UUID)YesYour app UUID from the dashboard
emailstringYesValid email address (unique per project)
passwordstringYesMin 8 characters
namestringNoFull name (max 255)

Example Request

curl
curl -X POST https://your-domain.com/api/v1/register \
  -H "Content-Type: application/json" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -d '{
    "app_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "email": "jane@example.com",
    "password": "secret1234",
    "name": "Jane Doe"
  }'

Response

201 Created
{
  "success": true,
  "user": {
    "id": 42,
    "project_id": 1,
    "app_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "status": "active",
    "custom_claims": {},
    "metadata": null
  },
  "token": "1|AbCdEfGhIjKlMnOpQrStUvWxYz...",
  "message": "Registration successful"
}
Authentication

Login

POST /api/v1/login

Authenticate an existing app user and receive a Bearer token. Rate limited to 5 attempts per minute.

Request Body

FieldTypeRequiredDescription
app_uuidstringYesYour app UUID from the dashboard
emailstringYesRegistered email in this app
passwordstringYesAccount password

Example

curl
curl -X POST https://your-domain.com/api/v1/login \
  -H "Content-Type: application/json" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -d '{
    "app_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "email": "jane@example.com",
    "password": "secret1234"
  }'

Response

200 OK
{
  "success": true,
  "user": {
    "id": 42,
    "project_id": 1,
    "app_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "status": "active"
  },
  "token": "2|XyZaBcDeFgHiJkLmNoPqRsTuVwX...",
  "message": "Login successful"
}
Authentication

Logout

POST /api/v1/logout

Revoke the current user's Bearer token. The token can no longer be used after this call.

curl
curl -X POST https://your-domain.com/api/v1/logout \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{ "success": true, "message": "Logged out successfully" }
Authentication

Profile

Get Profile

GET /api/v1/me
curl
curl https://your-domain.com/api/v1/me \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{
  "id": 42,
  "project_id": 1,
  "app_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "status": "active",
  "custom_claims": { "role": "premium" },
  "metadata": { "onboarding_complete": true },
  "last_login_at": "2026-05-02T10:30:00Z"
}

Update Profile

PUT /api/v1/me
FieldTypeRequiredDescription
namestringNoDisplay name (max 255)
phonestringNoPhone number (max 20)
avatarstringNoAvatar URL (max 255)
curl
curl -X PUT https://your-domain.com/api/v1/me \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Jane Smith", "phone": "+1234567890" }'
Authentication

Device Tokens

POST /api/v1/auth/devices

Register a device for Firestack Relay, standards Web Push, or direct APNs delivery.

Request Body

FieldTypeRequiredDescription
transportstringNorelay, webpush, or apns; inferred from platform when omitted
tokenstringDependsAPNs token, serialized PushSubscription, or an optional relay installation secret
subscriptionobjectWeb onlyBrowser PushSubscription containing endpoint, keys.p256dh, and keys.auth
platformstringYesandroid, ios, or web
device_namestringNoHuman-readable device name
app_uuidstringYesThe app UUID this device belongs to
curl
curl -X POST https://your-domain.com/api/v1/auth/devices \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{
    "transport": "relay",
    "platform": "android",
    "device_id": "install_01JABC...",
    "device_name": "Pixel 8 Pro",
    "app_uuid": "550e8400-e29b-41d4-a716-446655440000"
  }'
Authentication

Password Reset

Two-step password reset flow. Only requires X-API-Key (no Bearer token).

Step 1 — Request Reset

POST /api/v1/auth/forgot-password
FieldTypeRequired
emailstringYes
curl
curl -X POST https://your-domain.com/api/v1/auth/forgot-password \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jane@example.com" }'

Step 2 — Reset Password

POST /api/v1/auth/reset-password
FieldTypeRequired
tokenstringYes
emailstringYes
passwordstringYes
password_confirmationstringYes
Collections

List Collections

GET /api/v1/collections

Returns a paginated list of all top-level collections in the project associated with your API key.

curl
curl "https://your-domain.com/api/v1/collections?page=1" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "users",
      "slug": "users",
      "document_count": 142
    }
  ],
  "meta": { "current_page": 1, "total": 8, "per_page": 15 }
}
Collections

Create Collection

POST /api/v1/collections

Create a new top-level collection. The slug is auto-generated from the name if not provided.

Request Body

FieldTypeRequiredDescription
namestringYesCollection name
descriptionstringNoCollection description
schemaobjectNoOptional JSON schema for document validation
is_publicbooleanNoWhether collection allows unauthenticated reads
curl
curl -X POST https://your-domain.com/api/v1/collections \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Products", "description": "Product catalog" }'

Other Collection Endpoints

GET /api/v1/collections/{slug}

Get collection details

PATCH /api/v1/collections/{slug}

Update collection name, description, or schema

DELETE /api/v1/collections/{slug}

Delete collection and all its documents

Collections

Documents

CRUD operations for documents within a collection. Documents store arbitrary JSON data encrypted at rest with per-project AES-256-CBC encryption.

Collection-Scoped (by slug & doc_id)

GET /api/v1/collections/{slug}/documents

List documents with optional query filters

POST /api/v1/collections/{slug}/documents

Create a new document (optional doc_id in body)

GET /api/v1/collections/{slug}/documents/{docId}

Get document by doc_id within collection

PUT /api/v1/collections/{slug}/documents/{docId}

Replace document data entirely

UUID-Scoped (globally unique)

GET /api/v1/documents/{uuid}

Fetch document by UUID

PUT /api/v1/documents/{uuid}

Full replace — replaces entire data field

PATCH /api/v1/documents/{uuid}

Merge — partial update (deep merge into existing data)

DELETE /api/v1/documents/{uuid}

Delete document and cascade to subcollections

Create Document Example

curl
curl -X POST https://your-domain.com/api/v1/collections/products/documents \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "name": "Widget Pro",
      "price": 29.99,
      "tags": ["gadget", "sale"]
    }
  }'
Collections

Querying

The documents list endpoint supports rich Firestack-style filtering, ordering, and pagination via query parameters.

GET /api/v1/collections/{slug}/documents

Simple Equality Filter

URL
/api/v1/collections/products/documents?where[status]=active

Operator-Based Filter

URL
/api/v1/collections/products/documents?where[price][op]=lte&where[price][value]=50
OperatorMeaningExample
eqEqual to (default)active
neNot equal toinactive
ltLess than100
lteLess than or equal100
gtGreater than0
gteGreater than or equal18
likeSQL LIKE pattern%smith%
inValue in array[1,2,3]
not-inValue not in array[4,5]
array-containsJSON array contains valueadmin

Ordering & Pagination

ParameterTypeDefaultDescription
order_bystringcreated_atAllowed: created_at, updated_at, doc_id
order_dirstringascasc or desc
pageinteger1Page number
per_pageinteger15Results per page (max 100)

Full Example

curl
curl -G "https://your-domain.com/api/v1/collections/products/documents" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  --data-urlencode "where[price][op]=lte" \
  --data-urlencode "where[price][value]=50" \
  --data-urlencode "where[status]=active" \
  --data-urlencode "order_by=created_at" \
  --data-urlencode "order_dir=desc" \
  --data-urlencode "per_page=20"
Collections

Subcollections

Documents can contain nested subcollections, just like Firestack. Subcollections are created under a parent document's UUID.

GET /api/v1/documents/{uuid}/collections

List subcollections under a document

POST /api/v1/documents/{uuid}/collections

Create a new subcollection (body: name)

GET /api/v1/documents/{uuid}/collections/{slug}/documents

List documents in a subcollection

POST /api/v1/documents/{uuid}/collections/{slug}/documents

Create document in subcollection

GET /api/v1/documents/{uuid}/collections/{slug}/documents/{docId}

Get subcollection document by doc_id

PUT /api/v1/documents/{uuid}/collections/{slug}/documents/{docId}

Replace subcollection document

Example: Create a Subcollection

curl
curl -X POST https://your-domain.com/api/v1/documents/abc-123-uuid/collections \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "reviews" }'
File Storage

Upload File

POST /api/v1/files

Upload a file using multipart form data. Max size 50MB. Rate limited to 10 uploads per minute. Files count against the project's storage quota.

Request Body (multipart/form-data)

FieldTypeRequiredDescription
filefileYesThe file to upload (max 50MB)
visibilitystringNopublic or private (default: private)
categorystringNoCategory tag, e.g. avatar, document
metadataobjectNoCustom JSON metadata
curl
curl -X POST https://your-domain.com/api/v1/files \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -F "file=@/path/to/image.png" \
  -F "visibility=public" \
  -F "category=avatar"

Response

201 Created
{
  "success": true,
  "data": {
    "id": 7,
    "uuid": "a1b2c3d4-...",
    "original_name": "image.png",
    "size": 204800,
    "mime_type": "image/png",
    "visibility": "public",
    "checksum": "sha256:abc123..."
  }
}
File Storage

List Files

GET /api/v1/files

Returns a paginated list of files in the project. Supports filtering by category, MIME type, and visibility.

Query Parameters

ParameterTypeDescription
categorystringFilter by category (e.g. avatar)
mime_typestringFilter by MIME type (e.g. image/png)
visibilitystringpublic or private
pageintegerPage number (default: 1)
curl
curl "https://your-domain.com/api/v1/files?category=avatar&visibility=public" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
File Storage

File Metadata

GET /api/v1/files/{file}

Retrieve metadata for a specific file including name, size, MIME type, visibility, checksum, and custom metadata.

curl
curl https://your-domain.com/api/v1/files/7 \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
File Storage

Signed URLs

GET /api/v1/files/{file}/url

Generate a time-limited signed URL for downloading a private file. Default expiry is 60 minutes.

Query Parameters

ParameterTypeDefaultDescription
minutesinteger60URL expiry in minutes
curl
curl "https://your-domain.com/api/v1/files/7/url?minutes=120" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."

Response

200 OK
{
  "success": true,
  "data": {
    "url": "https://your-domain.com/storage/signed/...",
    "expires_at": "2026-04-18T14:00:00Z"
  }
}
File Storage

Delete File

DELETE /api/v1/files/{file}

Permanently delete a file. The project's storage quota usage is automatically decremented.

curl
curl -X DELETE https://your-domain.com/api/v1/files/7 \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
Messaging

Channels

Channels are containers for real-time messaging. They can be group channels, project-wide channels, or direct message channels. All messaging events are broadcast via Laravel Reverb for real-time updates.

List Channels

GET /api/v1/channels

Returns channels the authenticated user is a member of.

Create Channel

POST /api/v1/channels
FieldTypeRequiredDescription
project_idintegerYesProject this channel belongs to
namestringYesChannel name (max 255)
descriptionstringNoChannel description (max 1000)
typestringNogroup or project
is_privatebooleanNoWhether channel requires invitation
member_idsarrayNoArray of user IDs to add as initial members
curl
curl -X POST https://your-domain.com/api/v1/channels \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": 1,
    "name": "General",
    "type": "group",
    "member_ids": [2, 3, 4]
  }'

Other Channel Endpoints

GET /api/v1/channels/{channel}

Get channel details

PATCH /api/v1/channels/{channel}

Update name, description, avatar, or archive status

DELETE /api/v1/channels/{channel}

Delete channel

GET /api/v1/channels/{channel}/members

List channel members

POST /api/v1/channels/{channel}/members

Add member (body: user_id, role)

DELETE /api/v1/channels/{channel}/members/{userId}

Remove member

PATCH /api/v1/channels/{channel}/members/{userId}/role

Update member role (admin/member)

Messaging

Direct Messages

POST /api/v1/channels/direct

Create or retrieve a direct message channel between you and another user. If a DM channel already exists, it is returned instead of creating a duplicate.

FieldTypeRequired
project_idintegerYes
user_idintegerYes
curl
curl -X POST https://your-domain.com/api/v1/channels/direct \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "project_id": 1, "user_id": 5 }'
Messaging

Messages

Send and manage messages within channels. Messages support threading via reply_to_id, and all message events are broadcast in real-time.

Send Message

POST /api/v1/channels/{channel}/messages
FieldTypeRequiredDescription
bodystringYesMessage content (max 10,000 chars)
typestringNotext, image, file, or reply
reply_to_idintegerNoMessage ID to reply to (threading)
metadataobjectNoCustom JSON metadata
curl
curl -X POST https://your-domain.com/api/v1/channels/1/messages \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Hello everyone!", "type": "text" }'

List, Update & Delete

GET /api/v1/channels/{channel}/messages

List messages (paginated, supports before_id cursor)

PUT /api/v1/messages/{message}

Edit a message (body, metadata)

DELETE /api/v1/messages/{message}

Delete a message

Messaging

Reactions

Add or remove emoji reactions on messages. Reactions are broadcast in real-time to all channel members.

Add Reaction

POST /api/v1/messages/{message}/reactions
curl
curl -X POST https://your-domain.com/api/v1/messages/42/reactions \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{ "emoji": "👍" }'

Remove Reaction

DELETE /api/v1/messages/{message}/reactions/{emoji}
curl
curl -X DELETE "https://your-domain.com/api/v1/messages/42/reactions/%F0%9F%91%8D" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
Messaging

Read Receipts & More

Additional messaging features: read receipts, typing indicators, search, mute, and pin.

POST /api/v1/channels/{channel}/read

Mark all messages in channel as read

POST /api/v1/channels/{channel}/typing

Broadcast typing indicator (body: is_typing boolean)

GET /api/v1/channels/{channel}/search

Search messages (query: q, per_page)

POST /api/v1/channels/{channel}/mute

Toggle channel mute for current user

POST /api/v1/channels/{channel}/pin

Toggle channel pin for current user

Search Example

curl
curl "https://your-domain.com/api/v1/channels/1/search?q=hello&per_page=20" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
Notifications

List Notifications

GET /api/v1/notifications

Returns a paginated list of notifications for the authenticated user. Notifications are created and sent from the web dashboard or admin panel — the API provides read-only access for client apps.

curl
curl "https://your-domain.com/api/v1/notifications" \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{
  "success": true,
  "data": [
    {
      "id": "uuid-here",
      "title": "Welcome!",
      "body": "Thanks for signing up.",
      "status": "sent",
      "sent_at": "2026-04-18T10:00:00Z"
    }
  ]
}
Notifications

Mark as Read

Mark Single Notification

PUT /api/v1/notifications/{notification}/read
curl
curl -X PUT https://your-domain.com/api/v1/notifications/uuid-here/read \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."

Mark All as Read

POST /api/v1/notifications/read-all
curl
curl -X POST https://your-domain.com/api/v1/notifications/read-all \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
Notifications

Device Tokens

Push notifications are sent to devices registered via POST /api/v1/auth/devices. Notifications are created and dispatched from the web dashboard with targeting options:

Target TypeDescription
allSend to all registered devices in the project
topicSend to devices subscribed to a specific topic
userSend to a specific user's devices
deviceSend to a specific device token

Note: Notifications are created and sent from the web dashboard or Filament admin panel, not via the REST API. The API only provides endpoints to list and mark notifications as read.

Remote Config

Get Config

Remote Config lets you manage feature flags and app settings without shipping a new build. Configs are managed from the web dashboard — the API is read-only for client apps.

Fetch All Values

GET /api/v1/config

Returns all configs for the current app and environment. Pass ?environment=staging to override (default: production).

curl
curl https://your-domain.com/api/v1/config \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{
  "success": true,
  "data": [
    { "key": "maintenance_mode", "value": false },
    { "key": "min_app_version", "value": "2.1.0" },
    { "key": "feature_dark_mode", "value": true }
  ]
}

Config Features

FeatureDescription
TypesString, Number, Boolean, JSON
EnvironmentsProduction, Staging, Development, or All
App ScopePer-app configs or global (null app_uuid)
Feature FlagsToggle features on/off with is_feature_flag
Version TargetingSet min_app_version / max_app_version
Remote Config

Get Single Key

GET /api/v1/config/{key}

Fetch a specific config key's value. The value is automatically cast to its declared type (string, number, boolean, or JSON).

curl
curl https://your-domain.com/api/v1/config/min_app_version \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..."
200 OK
{
  "success": true,
  "data": {
    "key": "min_app_version",
    "value": "2.1.0",
    "type": "string"
  }
}
Analytics

Log Event

POST /api/v1/events

Log a custom analytics event. Rate limited to 100 events per minute. Events are associated with the authenticated user and the app identified by the API key.

Request Body

FieldTypeRequiredDescription
event_namestringYesEvent identifier (max 255)
propertiesobjectNoCustom event properties
session_idstringNoSession identifier (max 100)
platformstringNoPlatform name (e.g. android, ios)
app_versionstringNoApp version string
device_infoobjectNoDevice metadata (model, OS, etc.)
curl
curl -X POST https://your-domain.com/api/v1/events \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "purchase_completed",
    "properties": {
      "product_id": "pro-monthly",
      "amount": 29.00
    },
    "platform": "android",
    "app_version": "2.3.1"
  }'
Analytics

Batch Events

POST /api/v1/events/batch

Log multiple events in a single request (max 100). Rate limited to 20 batch requests per minute. Ideal for buffering events on the client and flushing periodically.

Request Body

FieldTypeRequiredDescription
eventsarrayYesArray of event objects (1–100 items)
events[].event_namestringYesEvent identifier
events[].propertiesobjectNoCustom properties
events[].session_idstringNoSession ID
events[].platformstringNoPlatform name
curl
curl -X POST https://your-domain.com/api/v1/events/batch \
  -H "X-API-Key: fsk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer 1|AbCdEfGhIjKl..." \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      { "event_name": "page_view", "properties": { "page": "home" } },
      { "event_name": "button_click", "properties": { "button": "signup" } }
    ]
  }'

Response

200 OK
{
  "success": true,
  "data": { "count": 2 },
  "message": "2 events logged successfully"
}
Realtime

Broadcasting Auth

POST /api/v1/broadcasting/auth

Authenticate for private and presence WebSocket channels. This is called automatically by the Laravel Echo / Pusher client library — you typically don't call this manually.

Laravel Echo (JavaScript)
// Configure Echo to use your API auth endpoint
const echo = new Echo({
  broadcaster: 'reverb',
  authEndpoint: '/api/v1/broadcasting/auth',
  auth: {
    headers: {
      'X-API-Key': apiKey,
      'Authorization': `Bearer ${token}`
    }
  }
});
Realtime

Channel Types

FireStack broadcasts events on private channels via Laravel Reverb. Subscribe to these channels to receive real-time updates in your client app.

ChannelEventsAccess
project.{id}.collectionsDocumentCreated, DocumentUpdated, DocumentDeletedProject members
project.{id}.storageFileUploaded, FileDeletedProject members
channel.{id}MessageSent, MessageUpdated, MessageDeleted, ReactionAdded, ReactionRemoved, UserTypingChannel members
user.{id}NotificationSentAuthenticated user only

Example: Listen for New Messages

JavaScript (Laravel Echo)
echo.private(`channel.${channelId}`)
  .listen('MessageSent', (e) => {
    console.log('New message:', e.message);
  })
  .listen('UserTyping', (e) => {
    console.log(e.user.name, 'is typing...');
  });
Flutter SDK

Installation

The firestack package is a complete Flutter/Dart SDK for FireStack — available on pub.dev. It mirrors the Firebase Flutter SDK patterns you already know: auth state streams, Firestack-style queries, FieldValue operations, upload progress callbacks, and realtime listeners with auto-reconnect.

Add the Dependency

pubspec.yaml
dependencies:
  firestack: ^1.2.0
Terminal
dart pub get

SDK Capabilities

Auth state streams & token persistence
Firestack-style queries & FieldValue ops
Subcollections & collection group queries
Aggregate queries (count/sum/average)
Document cache (offline-first reads)
Upload progress & stream tracking
Messaging: channels, reactions, typing
Notifications: permission lifecycle & topics
Remote Config: fetch throttling & defaults
Analytics: user properties & batch events
WebSocket realtime with auto-reconnect
Multiple named app instances
Batch writes & transactions
Error handling with typed exceptions

Requirements

RequirementVersion
Dart SDK>=3.0.0 <4.0.0
Flutter3.x (for Flutter apps) or Dart-only
Flutter SDK

Initialization

Initialize Firestack once at app startup with your API key and app ID. All services (auth, Firestack, storage, etc.) are accessed through the app instance.

🆕 SDK v2.0 Breaking Change: The appId parameter is now required. Get your app ID from the FireStack dashboard under your app settings.

Basic Setup

Dart
import 'package:firestack/firestack.dart';

void main() async {
  final app = Firestack.initialize(
    apiKey: 'fsk_live_xxxxxxxxxxxx',
    appId: '550e8400-e29b-41d4-a716-446655440000', // REQUIRED: Your app UUID from dashboard
    baseUrl: 'https://your-domain.com', // default: https://firestack.co.za
  );

  // Access services
  final auth = app.auth;
  final Firestack = app.Firestack;
  final storage = app.storage;
  final messaging = app.messaging;
  final notifications = app.notifications;
  final config = app.remoteConfig;
  final analytics = app.analytics;
  final realtime = app.realtime;
}

Configuration Options

ParameterTypeDefaultDescription
apiKeyStringRequiredYour fsk_ API key
appIdString (UUID)RequiredYour app UUID from the dashboard
baseUrlStringhttps://firestack.co.zaSelf-hosted server URL
nameString[default]Named instance (for multi-project)
timeoutDuration30sHTTP request timeout
maxRetriesint3Auto-retry on 429/5xx/timeout
logLevelFirestackLogLevelnonenone, error, info, verbose

Multiple App Instances

Dart
// Default app
final app = Firestack.initialize(
  apiKey: 'fsk_main',
  appId: '550e8400-e29b-41d4-a716-446655440000',
);

// Named secondary app
final secondary = Firestack.initialize(
  apiKey: 'fsk_other',
  appId: '550e8400-e29b-41d4-a716-446655440001',
  name: 'secondary',
);

// Retrieve later by name
final main = Firestack.instance;
final other = Firestack.instanceFor(name: 'secondary');
Flutter SDK

Authentication

Firebase-compatible auth with state streams, token persistence, password management, and account lifecycle. SDK v2.0 features simplified registration and multi-tenant user isolation.

Auth State Stream

Dart
final auth = app.auth;

// Listen to auth state changes (like Firebase onAuthStateChanged)
auth.authStateChanges.listen((user) {
  if (user != null) {
    print('Signed in: \${user.name}');
    print('App UUID: \${user.appId}, Project ID: \${user.projectId}');
  } else {
    print('Signed out');
  }
});

// Token persistence (save to secure storage)
auth.setTokenPersistence((token) async {
  await secureStorage.write(key: 'token', value: token);
});

Register & Sign In (v2.0)

Dart
// Register (simplified - no password confirmation)
final user = await auth.signUp(
  email: 'alice@example.com',
  password: 'password',
  name: 'Alice', // Optional
);

// Sign in
await auth.signIn(
  email: 'alice@example.com',
  password: 'password',
);

// Restore existing session
auth.signInWithToken('existing-bearer-token');
final me = await auth.currentUser();

User Model (v2.0)

Dart
final user = await auth.currentUser();

// Basic fields
print(user.id);         // User ID
print(user.projectId);  // Project ID (multi-tenancy)
print(user.appId);      // App UUID (multi-tenancy)
print(user.name);       // Display name
print(user.email);      // Email
print(user.status);     // 'active', 'pending', 'disabled'

// Status helpers
if (user.isActive) { ... }
if (user.isPending) { ... }
if (user.isDisabled) { ... }

// Custom claims (for role-based access control)
if (user.hasClaim('admin')) {
  print('User is admin');
}
final role = user.getClaim<String>('role');
final isPremium = user.getClaim<bool>('premium');

// Metadata (custom user data)
final onboarded = user.getMetadata<bool>('onboarding_complete');

Password & Account Management

Dart
// Forgot password (sends reset email)
await auth.sendPasswordResetEmail(email: 'alice@example.com');

// Change password (authenticated)
await auth.changePassword(
  currentPassword: 'old',
  newPassword: 'new',
  newPasswordConfirmation: 'new',
);

// Email verification
await auth.sendEmailVerification();

// Refresh token
final newToken = await auth.refreshToken();

// Delete account
await auth.deleteAccount(password: 'password');

// Sign out
await auth.signOut();
Flutter SDK

Firestack

Firestack API with collections, documents, subcollections, queries, FieldValue operations, batch writes, transactions, aggregate queries, collection group queries, and an in-memory document cache.

CRUD Operations

Dart
final Firestack = app.Firestack;

// Create
final doc = await Firestack.collection('users').add({
  'name': 'Alice',
  'age': 30,
});

// Read
final fetched = await Firestack.collection('users').doc('alice').get();

// Update
await Firestack.collection('users').doc('alice').update({'age': 31});

// Delete
await Firestack.collection('users').doc('alice').delete();

// Check existence
final exists = await Firestack.collection('users').doc('alice').exists();

Queries

Dart
final results = await Firestack.collection('users').query((q) => q
    .where('age', isGreaterThan: 25)
    .where('status', isEqualTo: 'active')
    .where('tags', arrayContains: 'premium')
    .where('role', whereIn: ['admin', 'editor'])
    .where('deleted_at', isNull: true)
    .orderBy('created_at', descending: true)
    .select(['name', 'email', 'age'])
    .search('alice')
    .limit(10)
    .page(1));

// Pagination helpers
print('Page \${results.currentPage}/\${results.lastPage}');
print('Has next: \${results.hasNextPage}');
final names = results.map((doc) => doc.get<String>('name'));

Query Operators

MethodDescription
isEqualToExact match
isNotEqualToNot equal
isLessThan / isLessThanOrEqualToRange filters
isGreaterThan / isGreaterThanOrEqualToRange filters
arrayContainsJSON array contains value
whereIn / whereNotInValue in/not in set
isNullNull check
search()Full-text search
select()Field projection
orderBy()Sort results
limit() / page()Pagination

FieldValue Operations

Dart
await Firestack.collection('users').doc('alice').update({
  'login_count': FieldValue.increment(1),
  'tags': FieldValue.arrayUnion(['vip']),
  'temp': FieldValue.delete(),
  'updated_at': FieldValue.serverTimestamp(),
});

Batch Writes & Transactions

Dart
// Batch writes (atomic)
final batch = Firestack.batch();
batch.set(Firestack.collection('users').doc('bob'), {'name': 'Bob'});
batch.update(Firestack.collection('users').doc('alice'), {'role': 'admin'});
batch.delete(Firestack.collection('users').doc('charlie'));
await batch.commit();

// Transactions (read-then-write)
await Firestack.runTransaction((tx) async {
  final doc = await tx.get(Firestack.collection('accounts').doc('alice'));
  final balance = doc.get<int>('balance') ?? 0;
  tx.update(Firestack.collection('accounts').doc('alice'),
      {'balance': balance - 50});
  tx.update(Firestack.collection('accounts').doc('bob'),
      {'balance': FieldValue.increment(50)});
});

Aggregates & Collection Groups

Dart
// Aggregate queries
final count = await Firestack.collection('users').count();
final totalRevenue = await Firestack.collection('orders').sum('amount');
final avgAge = await Firestack.collection('users').average('age');

// Collection group queries (across all subcollections named 'comments')
final allComments = await Firestack.collectionGroup('comments').getDocs();

Document Cache

Dart
// Cache-only read (offline-first)
final cached = await Firestack.collection('users').doc('alice')
    .get(source: CacheSource.cache);

// Clear cache
Firestack.cache.clear();
Flutter SDK

Storage

Upload and manage files with progress tracking, signed URLs, batch operations, and stream-based upload tasks (like Firebase UploadTask).

Upload with Progress

Dart
final storage = app.storage;

// Simple upload with callback
final file = await storage.upload(
  filePath: 'photo.jpg',
  fileBytes: bytes,
  visibility: 'public',
  category: 'images',
  onProgress: (sent, total) => print('\$sent / \$total'),
);

// Stream-based upload (like Firebase UploadTask)
final task = storage.uploadWithProgress(
  filePath: 'video.mp4',
  fileBytes: videoBytes,
);
task.onProgress.listen((snap) {
  print('\${(snap.progress * 100).toStringAsFixed(1)}%');
});
final result = await task.future;

Download URLs & Files

Dart
// Signed download URL with expiry
final url = await storage.getDownloadUrl('file-uuid', minutes: 30);
print('URL: \${url.url}');
print('Expired: \${url.isExpired}');

// List files with pagination
final files = await storage.list(
  category: 'images',
  page: 1,
  perPage: 20,
);

Batch Delete & Copy

Dart
// Delete multiple files
await storage.deleteFiles(['uuid1', 'uuid2']);

// Copy a file
final copy = await storage.copyFile('uuid', visibility: 'private');
Flutter SDK

Messaging

Full-featured instant messaging: channels, direct messages, reactions, read receipts, typing indicators, search, mute, and pin.

Dart
final messaging = app.messaging;

// List channels
final channels = await messaging.channels(projectId: 1);

// Create group channel
final channel = await messaging.createChannel(
  projectId: 1,
  name: 'general',
  memberIds: [2, 3],
);

// Create or get direct message channel
final dm = await messaging.createDirectChannel(
  projectId: 1,
  userId: 2,
);

// Send message
final msg = await messaging.sendMessage(
  channelId: channel.id,
  body: 'Hello!',
);

// Edit & delete
await messaging.updateMessage(msg.id, body: 'Hello! (edited)');
await messaging.deleteMessage(msg.id);

// Reactions
await messaging.addReaction(messageId: msg.id, emoji: '\u{1F44D}');
await messaging.removeReaction(messageId: msg.id, emoji: '\u{1F44D}');

// Read receipts & typing
await messaging.markAsRead(channel.id);
await messaging.sendTyping(channel.id);

// Search, mute, pin
final results = await messaging.searchMessages(channel.id, query: 'hello');
await messaging.toggleMute(channel.id);
await messaging.togglePin(channel.id);
Flutter SDK

Notifications

Complete push notification lifecycle: permission requests, device token registration, topic subscriptions, foreground message handling, and notification management.

Setup & Permissions

Dart
final notifications = app.notifications;

// 1. Configure platform & app
notifications.configure(platform: 'android', appId: 'com.example.app');

// 2. Set up native permission handler
notifications.setPermissionRequestHandler(() async {
  final status = await Permission.notification.request();
  return NotificationSettings(
    authorizationStatus: status.isGranted
        ? NotificationAuthorizationStatus.authorized
        : NotificationAuthorizationStatus.denied,
  );
});

// 3. Set up device token provider (e.g. from Firebase Messaging)
notifications.setTokenProvider(() async {
  return await FirebaseMessaging.instance.getToken();
});

// 4. Request permission (auto-registers device)
final settings = await notifications.requestPermission();
if (settings.isAuthorized) print('Push enabled!');

Topics & Foreground

Dart
// Foreground message handling
notifications.onMessage((notification) {
  print('Received: \${notification.title}');
});

// Topic subscriptions
await notifications.subscribeToTopic('promotions');
await notifications.unsubscribeFromTopic('promotions');

Manage Notifications

Dart
// List (optionally filter to unread)
final notifs = await notifications.list(unreadOnly: true);
final unread = await notifications.unreadCount();

// Mark read
await notifications.markAsRead('notification-id');
await notifications.markAllAsRead();

// Delete
await notifications.delete('notification-id');
await notifications.deleteAll();
Flutter SDK

Remote Config

Firebase-compatible remote config with defaults, fetch throttling, type-safe getters, and feature flag helpers.

Dart
final config = app.remoteConfig;

// Set defaults (used before first fetch)
config.setDefaults({
  'dark_mode': false,
  'max_items': 50,
  'welcome_msg': 'Hello!',
});

// Set minimum fetch interval (throttling)
config.minimumFetchInterval = Duration(minutes: 5);

// Fetch and activate in one call
final updated = await config.fetchAndActivate();

// Type-safe cached getters
final darkMode = config.getBool('dark_mode');
final limit = config.getInt('max_items', defaultValue: 50);
final ratio = config.getDouble('ratio', defaultValue: 1.0);
final name = config.getString('app_name', defaultValue: 'My App');

// Feature flags
if (config.isFeatureEnabled('new_ui')) {
  // show new UI
}

// Fetch status
print(config.lastFetchStatus); // success, failure, throttled
print(config.lastFetchTime);
Flutter SDK

Analytics

Event logging with user identity, user properties, screen tracking, convenience events, and batch support.

Dart
final analytics = app.analytics;

// Set user identity (like Firebase setUserId)
analytics.setUserId('user-123');

// Set user properties (auto-merged into all events)
analytics.setUserProperty(name: 'subscription', value: 'premium');

// Set defaults for all events
analytics.setDefaults(platform: 'android', appVersion: '2.0.0');

// Screen tracking
await analytics.logScreenView(
  screenName: 'Home',
  screenClass: 'HomePage',
);

// Convenience events
await analytics.logLogin(method: 'email');
await analytics.logSignUp(method: 'google');

// Custom events
await analytics.logEvent(
  name: 'purchase',
  properties: {'item_id': '42', 'price': '9.99'},
);

// Batch events (max 100)
await analytics.logBatch([
  AnalyticsEvent(name: 'item_view', properties: {'item_id': '42'}),
  AnalyticsEvent(name: 'add_to_cart', properties: {'qty': '1'}),
]);
Flutter SDK

Realtime

WebSocket-based realtime with auto-reconnect, heartbeat, Firestack snapshot streams, and message streams — powered by Laravel Reverb.

Connect

Dart
final realtime = app.realtime;

realtime.configure(
  host: 'your-domain.com',
  port: 8080,
  scheme: 'ws', // or 'wss' for SSL
  appKey: 'reverb-app-key',
  appSecret: 'reverb-app-secret',
);

await realtime.connect();

// Monitor connection state
realtime.stateStream.listen((state) => print('\$state'));

Document & Message Events

Dart
// Listen for document changes
await realtime.onDocumentCreated(projectId, (data) {
  print('New document: \$data');
});
await realtime.onDocumentUpdated(projectId, (data) => print(data));

// Listen for messaging events
await realtime.onMessageReceived(channelId, (data) => print(data));
await realtime.onTyping(channelId, (data) => print(data));

Firestack Snapshot Streams

Dart
// Collection snapshots (like Firebase onSnapshot)
Firestack.collection('users').snapshots(projectId: 1).listen((docs) {
  print('\${docs.length} users');
});

// Single document snapshots
Firestack.collection('users').doc('alice').snapshots(projectId: 1).listen((doc) {
  print('Alice: \${doc?.data}');
});

// Raw streams
realtime.snapshotStream(projectId: 1, event: 'document.created').listen(print);
realtime.messageStream(channelId).listen(print);
Flutter SDK

Error Handling

All SDK methods throw FirestackException on failure, with typed helpers for common error scenarios.

Dart
try {
  await app.auth.signIn(
    email: 'wrong@email.com',
    password: 'wrong',
  );
} on FirestackException catch (e) {
  print('\${e.statusCode}: \${e.message}');

  if (e.isUnauthorized) print('Bad credentials');
  if (e.isRateLimited) print('Too many requests — retry after \${e.statusCode}');
  if (e.isValidationError) print('Errors: \${e.errors}');
  if (e.isNotFound) print('Resource not found');
  if (e.isServerError) print('Server error');
}

Exception Properties

PropertyTypeDescription
statusCodeintHTTP status code
messageStringError message from the server
errorsMap?Field-level validation errors
isUnauthorizedbool401 — invalid credentials or expired token
isRateLimitedbool429 — too many requests
isValidationErrorbool422 — request body failed validation
isNotFoundbool404 — resource not found
isServerErrorbool500+ — server error

Auto-Retry

The SDK automatically retries requests that fail with 429 (rate limited), 5xx (server errors), or timeout — up to maxRetries times (default 3) with exponential backoff. You can configure this with Firestack.initialize(maxRetries: 2).

Flutter SDK

CLI Tool

The firestack_cli package provides a command-line interface for managing your FireStack backend directly from the terminal.

Installation

Terminal
# Install from source
cd firestack_cli
dart pub get

# Run directly
dart run bin/firestack.dart

# Or compile to native binary
dart compile exe bin/firestack.dart -o firestack
./firestack

Setup

Terminal
# Initialize project (creates firestack.json)
firestack init --api-key fsk_live_xxx --url https://your-domain.com

# Sign in
firestack auth login --email alice@example.com --password secret

# Check connection
firestack status

Available Commands

CommandDescription
firestack initInitialize project configuration
firestack statusCheck connection and auth status
firestack auth loginSign in
firestack auth registerCreate a new account
firestack auth profileShow current user profile
firestack auth logoutSign out
firestack collections listList all collections
firestack collections createCreate a collection
firestack collections showShow collection details
firestack collections deleteDelete a collection
firestack docs listList documents in a collection
firestack docs getGet a document by ID
firestack docs addAdd a new document
firestack docs setSet (upsert) a document
firestack docs deleteDelete a document