Skip to content

Organization Onboarding

Complete guide to creating organizations in Fermi.

Overview

Organizations are the core entities in Fermi that contain users, service actors, and configuration. Partners create organizations with an API key. Fermi app signup uses a user JWT on a different path (POST /api/v1/organisations) — do not use that for API-key provisioning.

Prerequisites

  • An API key with provision:org:create scope

Creating an Organization

Create organizations using API keys for programmatic provisioning.

curl -X POST https://api.fermi.dev/public/v1/identity/organisations/api-key \
  -H "Authorization: Bearer fmk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Organization",
    "domain": "https://org.example.com"
  }'

Request Parameters:

Parameter Type Required Description
name string Yes Name of the organization
domain string Yes Must be a URL (for example https://org.example.com). The API stores the root domain
theme object No Theme configuration object
isSignupAllowed boolean No Allow user signup (default: false when omitted)
domainSlug string No 2–63 lowercase alphanumeric plus hyphens
industry string No Industry identifier
companySize number No Number of employees
companyUrls string[] No Array of company URLs
documentUploads string[] No Array of document upload paths
operatingRegions string No Operating regions (comma-separated)
brief string No Company description

Response:

{
  "organisation": {
    "id": "507f1f77bcf86cd799439011",
    "name": "My Organization",
    "domain": "org.example.com",
    "parentOrganisationId": "507f1f77bcf86cd799439012",
    "createdAt": "2025-01-01T00:00:00Z"
  }
}

Create Organization with API Key

Python Example

import requests
import os

api_key = os.getenv("FERMI_API_KEY")

def create_organization_with_api_key(name: str, domain: str):
    response = requests.post(
        "https://api.fermi.dev/public/v1/identity/organisations/api-key",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        },
        json={
            "name": name,
            "domain": domain,
            "industry": "Technology",
            "companySize": 50
        }
    )

    if response.status_code == 201:
        org = response.json()
        print(f"Created organization: {org['organisation']['id']}")
        return org['organisation']['id']
    else:
        print(f"Error: {response.status_code} - {response.text}")
        return None
JavaScript Example

const apiKey = process.env.FERMI_API_KEY;

async function createOrganizationWithApiKey(name, domain) {
  try {
    const response = await fetch('https://api.fermi.dev/public/v1/identity/organisations/api-key', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: name,
        domain: domain,
        industry: 'Technology',
        companySize: 50
      })
    });

    if (response.ok) {
      const result = await response.json();
      console.log(`Created organization: ${result.organisation.id}`);
      return result.organisation.id;
    } else {
      console.error(`Error: ${response.status} - ${await response.text()}`);
      return null;
    }
  } catch (error) {
    console.error('Error creating organization:', error);
    return null;
  }
}

// Usage
createOrganizationWithApiKey('My Organization', 'https://myorg.example.com');

Organization Onboarding Questions & Answers

After creating an organization, complete onboarding with a service actor token.

Partner Chat API (this page): POST /public/v1/analytics/chat with "action": "onboarding". Live step values are 1 through 5. step: 0 returns "Invalid step number: 0. Must be between 1 and 5."

Product UI: the Fermi app uses a 5-chapter REST flow (Business, Processes, Operations, Performance, Technology), each with baseline / follow-up / review. That surface is also callable with a service actor token:

GET https://api.fermi.dev/core-analytics/onboarding/api/v2/tenant/{tenantId}/user/{userId}/status
GET https://api.fermi.dev/core-analytics/onboarding/api/v2/tenant/{tenantId}/user/{userId}/steps/{step}/sub-steps/{subStep}/questions
POST https://api.fermi.dev/core-analytics/onboarding/api/v2/tenant/{tenantId}/user/{userId}/steps/{step}/sub-steps/{subStep}/answers

step is 1–5. subStep is 1 (baseline), 2 (follow-up), or 3 (review). Keep using the Chat API below if you want the single-endpoint partner flow.

Conversation Flow

Step-by-Step Workflow

  1. Initial request (step 1): send "action": "onboarding", "step": 1, and "conversation_items": []. The API returns two questions.
  2. Steps 2–5: send answers in conversation_items with the next step. The API returns two new questions (or completed: true when finished).

Chat API for Onboarding

Process onboarding conversations and get contextual follow-up questions.

Initial Call (Step 1)

Start with step 1 and an empty conversation_items array:

curl -X POST https://api.fermi.dev/public/v1/analytics/chat \
  -H "Authorization: Bearer your_service_actor_token" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "onboarding",
    "conversation_items": [],
    "step": 1
  }'

Request Fields:

Field Type Required Description
action string Yes Must be "onboarding"
conversation_items array No Previous Q&A pairs
step integer Yes for a new conversation 15. Do not send 0

Response (Step 1):

{
  "success": "true",
  "error": "No Errors",
  "questions": [
    {
      "question": "Describe your core business offering and key services or products.",
      "options": [
        "B2B Enterprise",
        "Small and Medium Businesses",
        "Individual Consumers",
        "Government/Public Sector"
      ]
    },
    {
      "question": "What outcomes or metrics are you looking to improve?",
      "options": [
        "Reduce response time",
        "Increase accuracy of results",
        "Lower operational or infrastructure cost",
        "Improve scalability and system reliability"
      ]
    }
  ],
  "step": 1,
  "completed": false,
  "total_conversations_used": 0
}

Response Fields:

Field Type Description
success string "true" for successful requests, "false" for errors
error string Error message if success is "false", otherwise "No Errors"
questions array List of follow-up questions with suggested options
step integer The next step number in the conversation flow
completed boolean true when onboarding is complete, false otherwise
total_conversations_used integer Total number of Q&A pairs used for generating context

Subsequent Calls (Step 2+)

Continue with the next step and the Q&A pairs from the previous response:

curl -X POST https://api.fermi.dev/public/v1/analytics/chat \
  -H "Authorization: Bearer your_service_actor_token" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "onboarding",
    "conversation_items": [
      {
        "question": "Describe your core business offering and key services or products.",
        "answer": "We provide cloud-based analytics solutions for e-commerce businesses."
      },
      {
        "question": "What outcomes or metrics are you looking to improve?",
        "answer": "We want to improve customer retention rate by 20% and reduce churn."
      }
    ],
    "step": 2
  }'

Response (Step 2+):

{
  "success": "true",
  "error": "No Errors",
  "questions": [
    {
      "question": "What specific market segments within e-commerce do you serve?",
      "options": [
        "Fashion and Apparel",
        "Electronics and Technology",
        "Food and Beverage",
        "Health and Beauty"
      ]
    }
  ],
  "step": 2,
  "completed": false,
  "total_conversations_used": 2
}

Completion (After Step 6)

Request (Step 6):

curl -X POST https://api.fermi.dev/public/v1/analytics/chat \
  -H "Authorization: Bearer your_service_actor_token" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "onboarding",
    "step": 6,
    "conversation_items": [
      {
        "question": "What is your timeline for implementing these improvements?",
        "answer": "We'\''re aiming for Q2 2025 launch, with beta testing starting in April."
      },
      {
        "question": "What resources do you have available for this initiative?",
        "answer": "We have a dedicated team of 5 engineers, 2 data scientists, and $500K budget allocated."
      }
    ]
  }'

Response:

{
  "success": "true",
  "error": "No Errors",
  "questions": [],
  "conversation_id": "507f1f77bcf86cd799439016",
  "step": 7,
  "completed": true,
  "total_conversations_used": 12
}

Organization Onboarding Data Management

Retrieving Stored Conversations

Get stored onboarding conversations for analysis or review:

curl -X GET "https://api.fermi.dev/public/v1/analytics/retrieve/association/conversation?step=4" \
  -H "Authorization: Bearer your_service_actor_token"

Response:

{
  "success": true,
  "conversations": [
    {
      "tenant_id": "507f1f77bcf86cd799439011",
      "user_id": "user123",
      "step": 1,
      "convo": [
        {
          "question": "Describe your core business offering and key services or products.",
          "answer": "We provide cloud-based analytics solutions for e-commerce businesses."
        },
        {
          "question": "What specific market segments within e-commerce do you serve?",
          "answer": "We focus on Fashion and Apparel, particularly direct-to-consumer brands."
        }
      ],
      "created_at": "2025-01-01T00:00:00Z"
    }
  ],
  "total_count": 1,
  "error": null
}

Example Usage

Python Example

import requests

def start_onboarding_conversation(token):
    """Start onboarding with step 1."""
    response = requests.post(
        "https://api.fermi.dev/public/v1/analytics/chat",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        },
        json={
            "action": "onboarding",
            "conversation_items": [],
            "step": 1
        }
    )

    if response.status_code == 200:
        result = response.json()
        if result.get("success") == "true":
            return result
    return None

def continue_onboarding_conversation(token, conversation_items, step):
    """Continue onboarding with step 2–5 (include conversation_items)"""
    response = requests.post(
        "https://api.fermi.dev/public/v1/analytics/chat",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        },
        json={
            "action": "onboarding",
            "conversation_items": conversation_items,
            "step": step
        }
    )

    if response.status_code == 200:
        result = response.json()
        if result.get("success") == "true":
            return result
    return None

token = "your_service_actor_token"

# Start onboarding
initial_response = start_onboarding_conversation(token)

while True:  
    if not response:
        print("No response from server")
        break


    questions = response.get("questions", [])
    conversation_items = [
        {"question": q["question"], "answer": "My answer"}
        for q in questions
    ]

    if response.get("completed") is True:
        print("Onboarding completed")
        break

    step += 1
    response = continue_onboarding_conversation(
        token,
        conversation_items,
        step
    )

JavaScript Example

async function startOnboardingConversation(token) {
  const response = await fetch('https://api.fermi.dev/public/v1/analytics/chat', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      action: 'onboarding',
      conversation_items: [],
      step: 1
    })
  });

  if (response.ok) {
    const result = await response.json();
    if (result.success === "true") {
      return result;
    }
  }
  return null;
}

async function continueOnboardingConversation(token, conversationItems, step) {
  const response = await fetch('https://api.fermi.dev/public/v1/analytics/chat', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      action: 'onboarding',
      conversation_items: conversationItems,
      step: step
    })
  });

  if (response.ok) {
    const result = await response.json();
    if (result.success === "true") {
      return result;
    }
  }
  return null;
}

(async () => {
  const token = 'your_service_actor_token';

  let step = 1;
  let response = await startOnboardingConversation(token);

  while (true) { // do-while equivalent
    if (!response) {
      console.log('No response from server');
      break;
    }

    const questions = response.questions || [];

    const conversationItems = questions.map(q => ({
      question: q.question,
      answer: 'My answer'
    }));

    if (response.completed === true) {
      console.log('Onboarding completed');
      break;
    }

    step += 1;
    response = await continueOnboardingConversation(
      token,
      conversationItems,
      step
    );
  }
})();

Error Handling

Authentication Errors

Missing or Invalid Token

{
  "error": "Unauthorized",
  "message": "Invalid or missing authentication token"
}

Solution: Ensure you're using a valid authentication token in the Authorization header.

Missing API Key Scope

{
  "error": "Forbidden",
  "message": "Missing required scope: provision:org:create"
}

Solution: Ensure your API key has the provision:org:create scope for organization creation.

Validation Errors

Invalid Domain

{
  "error": "Bad Request",
  "message": "Domain already exists or is invalid"
}

Solution: Use a unique, valid domain name that follows URL format (e.g., company.example.com).

Missing Required Fields

{
  "error": "Bad Request",
  "message": "Missing required field: name"
}

Solution: Ensure all required fields are provided in your request.

Invalid Organization ID

{
  "error": "Not Found",
  "message": "Organization not found"
}

Solution: Verify the organization ID exists and you have access to it.

Permission Errors

Access Denied

{
  "error": "Forbidden",
  "message": "Access denied to organization"
}

Solution: Ensure your authentication token has permission to access the requested organization.

Rate Limiting

{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded"
}

Solution: Implement exponential backoff and respect rate limits.

Server Errors

Internal Server Error

{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred"
}

Solution: Retry the request with exponential backoff, or contact support if the issue persists.

Answer delegated onboarding questions

Teammates receive a tokenized link.

GET https://app.fermi.dev/api/onboarding/delegation/public?token=TOKEN

Minimum token length is 8. Response fields: organization_name, assigner_name, questions[] (assignment_id, question_id, question, question_type, options, status, due_date), batch_status.

POST https://app.fermi.dev/api/onboarding/delegation/public/answer

{
  "token": "TOKEN",
  "answers": [
    {
      "assignment_id": "ASSIGNMENT_ID",
      "answer": ["The process owner is Finance"]
    }
  ]
}

Response: { "answered_count": 1, "batch_status": "..." }

Organization public details (no partner token): GET https://api.fermi.dev/backend/api/v1/organisations/details?domain=example.com. domain (or an organization id) is required. Omitting it returns an error (domainOrIdRequired).

Delegation GET requires token (minimum length 8). Delegation POST requires token and answers.