API Reference

Overview

The IntelliToggle API is a REST API that enables you to manage feature flags, projects, tenants, and user access programmatically. This reference covers all available endpoints, request/response formats, and authentication methods.

The canonical documented API surface is versioned under /api/v1/…​. Legacy aliases such as /api/…​, /auth/…​, /oauth/…​, and older admin-domain paths may remain implemented temporarily during migration, but new integrations should use the versioned routes documented here.

Base Information

Base URL

https://api.intellitoggle.com/

API Version

1.0.0

Protocol

HTTPS

Authentication

Firebase ID token for dashboard/user actions; OAuth2 Client Credentials for backend integrations

Rate Limiting

Yes (see response headers)

Response Headers

All successful API responses include rate limiting information:

  • RateLimit-Limit - Maximum requests allowed

  • RateLimit-Remaining - Remaining requests in current window

  • RateLimit-Reset - Time when rate limit resets

Authentication

Bearer Authentication

Most endpoints require authentication using Firebase ID token bearer auth.

Authorization: Bearer <your-jwt-token>

OAuth 2.0

For machine-to-machine authentication, use OAuth 2.0 client credentials flow. This is the correct authentication model for third-party backend services. User Firebase tokens are for dashboard and user-session calls, not production service-to-service integrations.

The client credentials flow does not use redirect URLs. Create credentials in the same IntelliToggle environment you call at runtime; development credentials should be sent to the development API, and production credentials should be sent to https://api.intellitoggle.com.

POST /api/v1/oauth/token

Exchange client credentials for an access token.

Request Body (application/x-www-form-urlencoded):

grant_type=client_credentials
client_id=<your-client-id>
client_secret=<your-client-secret>
scope=flags:read flags:write

Response:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "flags:read flags:write"
}

Recommended runtime evaluation scopes:

  • flags:read

  • flags:evaluate

  • projects:read

Only request flags:write or projects:write when the integrating service needs to create or modify IntelliToggle resources.

Token Smoke Test

curl -sS -X POST https://api.intellitoggle.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=$INTELLITOGGLE_CLIENT_ID" \
  --data-urlencode "client_secret=$INTELLITOGGLE_CLIENT_SECRET" \
  --data-urlencode "scope=flags:read flags:evaluate"

User Authentication

The canonical user-auth contract follows the Dart Cloud Functions bootstrap model:

  • frontend authenticates with Firebase first

  • frontend sends the Firebase idToken to the backend bootstrap endpoint

  • backend responds with active and canonical tenant context

  • protected routes use Authorization: Bearer <Firebase idToken>

POST /api/v1/auth/signup

Bootstrap a newly created Firebase account.

Request Body:

{
  "idToken": "firebase_id_token",
  "email": "newuser@example.com",
  "password": "securepassword123"
}

POST /api/v1/auth/login

Bootstrap an existing Firebase session.

Request Body:

{
  "idToken": "firebase_id_token"
}

POST /api/v1/auth/signin/google

POST /api/v1/auth/signin/github

POST /api/v1/auth/signin/microsoft

Bootstrap a federated Firebase session for the selected provider.

Request Body:

{
  "idToken": "firebase_id_token",
  "providerName": "google"
}

Response (200 OK):

{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": {
      "uid": "firebase_uid",
      "email": "user@example.com",
      "tenant_id": "tenant_123",
      "role": "admin"
    },
    "subscription": {
      "plan": "standard",
      "status": "active",
      "billingCycle": "annual"
    },
    "tenants": [
      {
        "tenant_id": "tenant_123",
        "tenant_name": "Acme",
        "role": "admin"
      }
    ],
    "active_tenant_id": "tenant_123",
    "active_tenant_role": "admin",
    "canonical_tenant_id": "tenant_123",
    "canonical_role": "admin"
  }
}

POST /api/v1/auth/forgot-password

Send password reset email.

Request Body:

{
  "email": "user@example.com"
}

POST /api/v1/auth/update-password

Update user password (requires authentication).

Request Body:

{
  "currentPassword": "oldpassword",
  "newPassword": "newpassword123"
}

POST /api/v1/auth/logout

Logout the current backend auth session. The frontend also clears the local Firebase session.

Response (200 OK):

{
  "success": true
}

Feature Flags

GET /api/v1/flags

List all feature flags for tenant.

Headers: * X-Tenant-ID (required) - Tenant identifier * X-Environment (optional, default: production) - Environment name

Response (200 OK):

[
  {
    "id": "flag_123",
    "key": "new-checkout-flow",
    "name": "New Checkout Flow",
    "type": "boolean",
    "enabled": true,
    "defaultValue": false,
    "description": "Enable the new checkout flow",
    "tags": ["ui", "checkout"],
    "projectId": "proj_456",
    "createdAt": "2024-01-15T10:00:00Z",
    "updatedAt": "2024-01-20T15:30:00Z"
  }
]

POST /api/v1/flags

Create a new feature flag.

Headers: * X-Tenant-ID (required) * X-Environment (optional, default: production)

Request Body:

{
  "key": "new-checkout-flow",
  "name": "New Checkout Flow",
  "type": "boolean",
  "defaultValue": false,
  "description": "Enable the new checkout flow",
  "tags": ["ui", "checkout"]
}

Flag Types: * boolean - True/false values * string - Text values * number - Numeric values * json - Complex JSON objects

Response (201 Created): Returns the created flag object.

GET /api/v1/flags/{flagKey}

Get specific feature flag details.

Path Parameters: * flagKey - Unique flag identifier

Headers: * X-Tenant-ID (required) * X-Environment (optional)

PATCH /api/v1/flags/{flagKey}

Update feature flag details.

Request Body:

{
  "name": "Updated Name",
  "description": "Updated description",
  "defaultValue": true,
  "tags": ["new-tag"]
}

DELETE /api/v1/flags/{flagKey}

Delete a feature flag.

Response (204 No Content): No response body.

POST /api/v1/flags/{flagKey}/enable

Enable a feature flag.

Response (200 OK):

{
  "success": true
}

POST /api/v1/flags/{flagKey}/disable

Disable a feature flag.

POST /api/v1/flags/{flagKey}/evaluate

Evaluate a feature flag for specific user context.

This endpoint supports OAuth2 bearer tokens for machine-to-machine evaluation. Include X-Tenant-ID and X-Environment so the request is unambiguous.

Headers: * Authorization: Bearer <oauth_access_token> * X-Tenant-ID: <tenant_id> * X-Environment: production * Content-Type: application/json

Request Body:

{
  "targetingKey": "user_123",
  "attributes": {
    "email": "user@example.com",
    "plan": "premium",
    "country": "US"
  },
  "environment": "production"
}

Response (200 OK):

{
  "enabled": true,
  "value": true,
  "reason": "RULE_MATCH"
}

GET /api/v1/flags/{flagKey}/history

Get change history for a feature flag.

Response (200 OK):

[
  {
    "id": "hist_123",
    "action": "enabled",
    "changes": {
      "enabled": {
        "from": false,
        "to": true
      }
    },
    "userId": "user_456",
    "userName": "John Doe",
    "timestamp": "2024-01-20T15:30:00Z"
  }
]

Flag Variations

GET /api/v1/flags/{flagKey}/variations

List all variations for a feature flag.

Response (200 OK):

[
  {
    "id": "var_123",
    "name": "Control",
    "value": false,
    "description": "Control group",
    "weight": 50
  },
  {
    "id": "var_456",
    "name": "Treatment",
    "value": true,
    "description": "Treatment group",
    "weight": 50
  }
]

POST /api/v1/flags/{flagKey}/variations

Add new variation to feature flag.

Request Body:

{
  "name": "New Variation",
  "value": true,
  "description": "Description of variation"
}

PATCH /api/v1/flags/{flagKey}/variations/{variationId}

Update existing flag variation.

DELETE /api/v1/flags/{flagKey}/variations/{variationId}

Delete flag variation.

Projects

GET /api/v1/projects

List all projects in tenant.

Headers: * X-Tenant-ID (required)

Response (200 OK):

[
  {
    "id": "proj_123",
    "name": "Mobile App Features",
    "description": "Feature flags for mobile application",
    "color": "#6366f1",
    "flagCount": 15,
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-20T12:00:00Z"
  }
]

POST /api/v1/projects

Create a new project.

Request Body:

{
  "name": "Mobile App Features",
  "description": "Feature flags for mobile application",
  "color": "#6366f1"
}

Response (201 Created): Returns the created project object.

GET /api/v1/projects/{projectId}

Get specific project details.

PATCH /api/v1/projects/{projectId}

Update project details.

Request Body:

{
  "name": "Updated Project Name",
  "description": "Updated description",
  "color": "#ef4444"
}

DELETE /api/v1/projects/{projectId}

Delete a project.

Project Flags

GET /api/v1/flags/projects/{projectId}/flags

List all flags within a specific project.

POST /api/v1/flags/projects/{projectId}/flags

Create a new flag within a project.

Request Body:

{
  "key": "test-feature1",
  "name": "Test Feature1",
  "type": "boolean",
  "defaultValue": false,
  "description": "Test flag",
  "flag_variations": [
    {
      "name": "Enabled",
      "value": true,
      "description": "Feature enabled state"
    },
    {
      "name": "Disabled",
      "value": false,
      "description": "Feature disabled state"
    }
  ]
}

GET /api/v1/flags/projects/{projectId}/flags/{flagKey}

Get specific flag within a project.

PATCH /api/v1/flags/projects/{projectId}/flags/{flagKey}

Update flag within a project.

Request Body:

{
  "status": "active",
  "name": "Updated Name",
  "description": "Updated description"
}

Status Values: * active - Flag is active * inactive - Flag is inactive * archived - Flag is archived

DELETE /api/v1/flags/projects/{projectId}/flags/{flagKey}

Delete flag from project.

POST /api/v1/flags/projects/{projectId}/flags/{flagKey}/enable

Enable flag within project.

POST /api/v1/flags/projects/{projectId}/flags/{flagKey}/disable

Disable flag within project.

Tenants

GET /api/v1/tenants

List all tenants for current user.

Response (200 OK):

[
  {
    "id": "tenant_123",
    "name": "Acme Corporation",
    "primaryEmail": "admin@acme.com",
    "contactName": "John Doe",
    "isSandbox": false,
    "subscription": {
      "plan": "premium",
      "status": "active"
    },
    "createdAt": "2024-01-01T00:00:00Z"
  }
]

POST /api/v1/tenants

Create a new tenant organization.

Request Body:

{
  "name": "Acme Corporation",
  "primaryEmail": "admin@acme.com",
  "contactName": "John Doe",
  "isSandbox": false
}

GET /api/v1/tenants/{tenantId}

Get specific tenant details.

Headers: * X-Tenant-ID (required)

PATCH /api/v1/tenants/{tenantId}

Update tenant information.

Request Body:

{
  "name": "Updated Name",
  "primaryEmail": "newemail@acme.com",
  "contactName": "Jane Smith"
}

DELETE /api/v1/tenants/{tenantId}

Delete a tenant.

GET /api/v1/tenants/{tenantId}/usage

Get tenant resource usage statistics.

Response (200 OK):

{
  "flags": 50,
  "evaluations": 1000000,
  "users": 10
}

Tenant Environments

GET /api/v1/tenants/{tenantId}/environments

List all environments in tenant.

Response (200 OK):

["production", "staging", "development"]

POST /api/v1/tenants/{tenantId}/environments

Add new environment to tenant.

Request Body:

{
  "environment": "qa"
}

DELETE /api/v1/tenants/{tenantId}/environments/{environment}

Remove environment from tenant.

Tenant Users

GET /api/v1/tenants/{tenantId}/users

List all users in tenant.

Response (200 OK):

[
  {
    "userId": "user_123",
    "email": "john@example.com",
    "displayName": "John Doe",
    "role": "admin",
    "lastAccessedAt": "2024-01-20T15:30:00Z",
    "createdAt": "2024-01-01T00:00:00Z"
  }
]

POST /api/v1/tenants/{tenantId}/users

Add user to tenant with specific role.

Request Body:

{
  "email": "newuser@example.com",
  "role": "developer"
}

Available Roles: * admin - Full access to all features * developer - Can manage flags and projects * viewer - Read-only access

DELETE /api/v1/tenants/{tenantId}/users/{userId}

Remove user from tenant.

Billing

POST /api/v1/billing/checkout

Create a Stripe checkout session for a new subscription or one-time payment. If the tenant already has a manageable Stripe subscription, subscription requests create a targeted Stripe Billing Portal subscription_update_confirm session for the selected plan price instead of a generic portal session.

Stripe portal configuration must allow every switchable plan price under subscription update products, otherwise Stripe can reject the targeted plan change flow.

Headers: * X-Tenant-ID (required)

Request Body:

{
  "type": "subscription",
  "planId": "standard",
  "successUrl": "https://app.intellitoggle.com/membership/membership-paid",
  "cancelUrl": "https://app.intellitoggle.com/membership",
  "referralId": "pk_ref_123",
  "promotekit_referral": "pk_ref_123",
  "promoCode": "SPRING25",
  "customerId": "cus_123",
  "discounts": [
    {
      "coupon": "coupon_123"
    }
  ]
}

Required Fields: * type - subscription or payment * successUrl * cancelUrl

Subscription Checkout Fields: * planId (required for type: "subscription")

Payment Checkout Fields: * productId (required for type: "payment") * amount (required for type: "payment")

Optional Fields: * customerId * promoCode * discounts * referralId - Canonical referral field * promotekit_referral - Legacy alias still accepted

When the caller is using PromoteKit, referralId and promotekit_referral should carry the actual PromoteKit referral value from window.promotekit_referral or the promotekit_referral cookie.

Available Subscription Plans: * standard - Standard plan * enhanced - Enhanced plan * enterprise - Enterprise plan * sandbox - Sandbox plan

Response (200 OK):

{
  "checkoutUrl": "https://checkout.stripe.com/...",
  "type": "subscription"
}

POST /api/v1/billing/portal

Create Stripe customer portal session.

Request Body:

{
  "returnUrl": "https://app.intellitoggle.com/billing"
}

GET /api/v1/billing/subscription

Get current subscription information.

Response (200 OK):

{
  "id": "sub_123",
  "plan": "premium",
  "status": "active",
  "currentPeriodStart": "2024-01-01T00:00:00Z",
  "currentPeriodEnd": "2024-02-01T00:00:00Z",
  "cancelAtPeriodEnd": false
}

GET /api/v1/billing/features/analytics

Check if tenant has access to analytics features.

Response (200 OK):

{
  "hasAccess": true
}

GET /api/v1/billing/features/experiments/check

Check if tenant has access to A/B testing features.

GET /api/v1/billing/features/multivariate/check

Check if tenant has access to multivariate testing.

OAuth Clients

POST /api/v1/oauth/clients

Create new OAuth client for API access.

Headers: * X-Tenant-ID (required)

Request Body:

{
  "name": "SDK Demo",
  "scopes": [
    "flags:read",
    "flags:write",
    "flags:evaluate",
    "projects:read",
    "projects:write"
  ]
}

Available Scopes: * flags:read - Read flag data * flags:write - Create and modify flags * flags:evaluate - Evaluate flags * projects:read - Read project data * projects:write - Create and modify projects

Response (201 Created):

{
  "clientId": "client_abc123",
  "clientSecret": "secret_xyz789",
  "name": "SDK Demo",
  "scopes": ["flags:read", "flags:write"],
  "createdAt": "2024-01-20T10:00:00Z"
}
Store the clientSecret securely. It will only be shown once.

Product Tours

GET /api/v1/users/{userId}/product-tours/{tourKey}

Get the status of a product tour for a user.

Path Parameters: * userId - User identifier (e.g., usr_123) * tourKey - Tour identifier (e.g., main-onboarding@v1)

Response (200 OK):

{
  "completed": false,
  "completed_at": null,
  "created_at": "2024-01-15T10:00:00Z",
  "updated_at": "2024-01-20T15:30:00Z"
}

PUT /api/v1/users/{userId}/product-tours/{tourKey}

Update the completion status of a product tour.

Headers: * Idempotency-Key (optional) - Ensure idempotent requests

Request Body:

{
  "completed": true,
  "completed_at": "2024-01-20T15:30:00Z"
}

DELETE /api/v1/users/{userId}/product-tours/{tourKey}

Reset product tour status to allow user to see it again.

Sandbox

GET /api/v1/sandbox/status

Get current sandbox environment status.

Headers: * X-Tenant-ID (required)

Response (200 OK):

{
  "active": true,
  "expiresAt": "2024-02-01T00:00:00Z"
}

GET /api/v1/sandbox/indicators

Get sandbox usage indicators and metrics.

Response (200 OK):

{
  "flagsUsed": 5,
  "evaluations": 1000
}

GET /api/v1/sandbox/demo-data

Get demo data for sandbox environment.

Response (200 OK):

{
  "flags": ["demo-flag-1", "demo-flag-2"],
  "users": ["demo-user-1", "demo-user-2"]
}

POST /api/v1/sandbox/cleanup/tenant

Reset sandbox tenant to clean state.

Response (200 OK):

{
  "success": true
}

POST /api/v1/sandbox/subscribe

Subscribe to sandbox environment updates.

Request Body:

{
  "email": "user@example.com",
  "name": "John Doe"
}

Notifications

GET /api/v1/notifications

List notifications for the authenticated user.

Query Parameters: * limit (optional, default: 20) * offset (optional, default: 0) * unreadOnly (optional, default: false)

Response (200 OK):

{
  "notifications": [],
  "totalCount": 0,
  "unreadCount": 0
}

GET /api/v1/notifications/{id}

Get a single notification by ID.

PATCH /api/v1/notifications/{id}/read

Mark a notification as read.

PATCH /api/v1/notifications/read-all

Mark all notifications as read for the authenticated user.

Response (200 OK):

{
  "success": true,
  "markedCount": 0
}

DELETE /api/v1/notifications/clear

Clear all notifications for the authenticated user.

Response (200 OK):

{
  "success": true,
  "clearedCount": 0
}

GET /api/v1/notifications/device-tokens

List device tokens for the authenticated user.

POST /api/v1/notifications/device-tokens

Register a device token for push notifications.

Request Body:

{
  "deviceToken": "device_token_value",
  "deviceName": "Chrome",
  "deviceType": "web"
}

DELETE /api/v1/notifications/device-tokens/{token}

Delete a device token.

POST /api/v1/notifications/send

Send a notification (internal/admin only).

Request Body:

{
  "userId": "user_123",
  "tenantId": "tenant_456",
  "title": "Notification title",
  "body": "Optional message body",
  "type": "system",
  "icon": "bell",
  "actionType": "link",
  "referenceId": "flag_123",
  "data": {
    "source": "system"
  }
}

Admin Endpoints

GET /api/v1/admin/users

List admin users (super_user only). List all users across the platform (admin only).

Response (200 OK): Returns array of admin user objects.

GET /api/v1/admin/users/system

List all platform users (super_user only).

PATCH /api/v1/admin/users/{userId}/deactivate

Deactivate an admin user (super_user only).

PATCH /api/v1/admin/users/{userId}/reactivate

Reactivate an admin user (super_user only).

PATCH /api/v1/admin/users/{userId}/role

Update an admin user role (super_user only).

Request Body:

{
  "role": "admin"
}

Available Roles: * viewer - Read-only admin access * admin - Admin operator access * super_user - Restricted Super Admin access, only for emails in ADMIN_SUPER_USER_EMAILS

POST /api/v1/admin/invitations

Invite a new admin user (super_user only).

Request Body:

{
  "email": "newadmin@company.com",
  "role": "admin"
}

Available Roles: * viewer - Read-only admin access * admin - Admin operator access

GET /api/v1/admin/invitations

List admin invitations (super_user only).

GET /api/v1/admin/invitations/{invitationId}

Get an admin invitation.

DELETE /api/v1/admin/invitations/{invitationId}

Revoke an admin invitation.

POST /api/v1/admin/invitations/{invitationId}/resend

Resend an admin invitation.

POST /api/v1/admin/invitations/accept

Accept an admin invitation for the authenticated user.

POST /admin/invite

Send invitation to new user (admin only).

Deprecated. Use POST /api/v1/admin/invitations for system admin access or POST /api/v1/tenants/{tenantId}/users for tenant invites.

Request Body (legacy):

{
  "email": "newuser@company.com",
  "role": "user"
}

Available Roles (legacy): * admin - Administrator * user - Regular user

GET /api/v1/admin/tenants

List all tenants (admin only).

Health & System

GET /health

Check API health status.

Response (200 OK):

{
  "status": "healthy",
  "timestamp": "2024-01-20T15:30:00Z"
}

GET /api/v1/version

Get current API version.

Response (200 OK):

{
  "version": "0.0.31",
  "build_number": "1",
  "status": "healthy",
  "timestamp": "2026-04-14T09:30:00Z",
  "environment": "production"
}

GET /

Basic API information.

Response (200 OK):

{
  "name": "IntelliToggle API",
  "version": "1.0.0"
}

Error Responses

All error responses follow RFC 9457 (Problem Details).

Error Structure

{
  "type": "https://docs.example.com/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "The requested resource was not found.",
  "instance": "req_01Hh88888"
}

Common Status Codes

Code Description

200

Success

201

Created

204

No Content

400

Bad Request - Invalid input

401

Unauthorized - Authentication required

403

Forbidden - Access denied

404

Not Found - Resource doesn’t exist

409

Conflict - Resource already exists

412

Precondition Failed

415

Unsupported Media Type

422

Unprocessable Entity - Validation failed

429

Too Many Requests - Rate limit exceeded

500

Internal Server Error

503

Service Unavailable

Validation Errors

Validation errors (422) include detailed field-level information:

{
  "type": "https://docs.example.com/errors/unprocessable-entity",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "Validation failed.",
  "errors": {
    "email": "Invalid email format",
    "password": "Password must be at least 8 characters"
  }
}

Rate Limiting

All endpoints are subject to rate limiting. Limits vary by subscription plan.

When rate limit is exceeded, you’ll receive a 429 response:

{
  "type": "https://docs.example.com/errors/too-many-requests",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Try again later."
}

Check response headers for rate limit information:

  • RateLimit-Limit - Maximum requests allowed

  • RateLimit-Remaining - Remaining requests

  • RateLimit-Reset - Reset time

Best Practices

Use Idempotency Keys

For critical operations (like creating subscriptions), use idempotency keys to prevent duplicate requests:

Idempotency-Key: unique-request-id-123

Handle Errors Gracefully

Always check status codes and handle errors appropriately. Don’t assume success.

Respect Rate Limits

Monitor RateLimit-Remaining header and implement exponential backoff when approaching limits.

Use Specific Scopes

When creating OAuth clients, request only the scopes you need. Follow the principle of least privilege.

Secure API Keys

  • Never commit API keys to version control

  • Rotate keys regularly

  • Use environment variables for key storage

  • Implement key rotation without downtime

Code Examples

cURL

# Create a feature flag
curl -X POST https://api.intellitoggle.com/api/v1/flags \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: tenant_123" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "new-feature",
    "name": "New Feature",
    "type": "boolean",
    "defaultValue": false
  }'