Felo API PlatformFelo API Platform

Configure Harness

Chat API

Integrate AI-powered conversational search with query analysis, web resources, and citations.

POST/v2/chat

Authentication

Bearer API key

Content type

application/json

Rate notes

Per API key and per user windows. Read X-RateLimit headers.

The Chat API enables you to integrate AI-powered conversational search into your applications. Send a natural language query and receive an intelligent answer generated from real-time web search results.

Overview

The Chat API performs the following operations:

  1. Analyzes and optimizes your query
  2. Searches the web for relevant information
  3. Ranks and filters search results
  4. Generates a comprehensive AI answer based on the sources
  5. Returns the answer along with source citations

Use Cases:

  • Conversational AI assistants
  • Research and information retrieval tools
  • Customer support chatbots with web knowledge
  • Educational applications
  • Content creation tools

Authentication

All requests to the Chat API require authentication using an API Key. Include your API Key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

See the Authentication Guide for details on obtaining and using your API Key.

Endpoint

POST https://openapi.felo.ai/v2/chat

Request

Headers

HeaderRequiredDescription
AuthorizationYesYour API Key in the format: Bearer YOUR_API_KEY
Content-TypeYesMust be application/json

Body Parameters

ParameterTypeRequiredDescriptionConstraints
querystringYesThe user's question or search query1-2000 characters

Example Request

{
  "query": "What are the latest developments in quantum computing?"
}

Response

Success Response (200 OK)

When the request is successful, the API returns a JSON response with the following structure:

{
  "status": "ok",
  "message": null,
  "data": {
    "id": "HabCj883yHLSXc8mWqu4Eq",
    "message_id": "18ea8517-5559-4f48-a355-1f8a79e73b71",
    "answer": "Recent developments in quantum computing include...",
    "query_analysis": {
      "queries": [
        "quantum computing latest developments 2025",
        "quantum computing breakthroughs"
      ]
    },
    "resources": [
      {
        "link": "https://example.com/quantum-news",
        "title": "Latest Quantum Computing Breakthroughs",
        "snippet": "Scientists have achieved a major milestone..."
      },
      {
        "link": "https://example.com/quantum-research",
        "title": "Quantum Computing Research 2025",
        "snippet": "New quantum algorithms show promise..."
      }
    ]
  }
}

Response Fields

FieldTypeDescription
statusstringResponse status: "ok" for success, "error" for failure
messagestring | nullAdditional message (null for successful requests)
dataobjectResponse data (only present when status is "ok")
data.idstringUnique identifier for this chat session
data.message_idstringUnique identifier for this specific message
data.answerstringAI-generated answer to the query
data.query_analysisobjectAnalysis of the user's query
data.query_analysis.queriesstring[]Optimized search queries derived from the user's input
data.resourcesarrayList of web sources used to generate the answer
data.resources[].linkstringURL of the source
data.resources[].titlestringTitle of the source page
data.resources[].snippetstringRelevant excerpt from the source

Error Response

When an error occurs, the API returns a JSON response with error details:

{
  "status": "error",
  "code": "INVALID_API_KEY",
  "message": "The provided API Key is invalid or has been revoked",
  "request_id": "req_abc123xyz789"
}

Error Response Fields

FieldTypeDescription
statusstringAlways "error" for error responses
codestringError code identifying the type of error
messagestringHuman-readable error description
request_idstringUnique identifier for this request (useful for support)

Error Codes

CodeHTTP StatusDescriptionSolution
INVALID_API_KEY401API Key is invalid, malformed, or revokedVerify your API Key is correct and active
MISSING_AUTHORIZATION401Authorization header is missingInclude the Authorization header in your request
MALFORMED_AUTHORIZATION401Authorization header format is incorrectUse format: Authorization: Bearer YOUR_API_KEY
MISSING_PARAMETER400Required parameter is missingEnsure query parameter is included in request body
INVALID_PARAMETER400Parameter value is invalidCheck that query is a non-empty string (1-2000 characters)
QUERY_TOO_LONG400Query exceeds maximum lengthShorten your query to 2000 characters or less
RATE_LIMITED429Too many requests in a short timeSlow down your request rate or contact support
CHAT_FAILED502Internal service errorRetry the request; contact support if the issue persists
SERVICE_UNAVAILABLE503Service temporarily unavailableWait and retry; check status page for updates

Examples

cURL

curl -X POST https://openapi.felo.ai/v2/chat \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What are the benefits of renewable energy?"
  }'

Response:

{
  "status": "ok",
  "message": null,
  "data": {
    "id": "HabCj883yHLSXc8mWqu4Eq",
    "message_id": "18ea8517-5559-4f48-a355-1f8a79e73b71",
    "answer": "Renewable energy offers numerous benefits including reduced greenhouse gas emissions, decreased air pollution, energy independence, job creation, and long-term cost savings. Solar, wind, and hydroelectric power are sustainable alternatives to fossil fuels that help combat climate change while providing reliable electricity.",
    "query_analysis": {
      "queries": [
        "renewable energy benefits",
        "advantages of renewable energy sources"
      ]
    },
    "resources": [
      {
        "link": "https://example.com/renewable-benefits",
        "title": "Top Benefits of Renewable Energy",
        "snippet": "Renewable energy sources provide clean, sustainable power..."
      }
    ]
  }
}

Python

import requests
import os

# Get API key from environment variable
api_key = os.environ.get('FELO_API_KEY')

url = 'https://openapi.felo.ai/v2/chat'
headers = {
    'Authorization': f'Bearer {api_key}',
    'Content-Type': 'application/json'
}
data = {
    'query': 'What are the benefits of renewable energy?'
}

try:
    response = requests.post(url, headers=headers, json=data)
    response.raise_for_status()  # Raise exception for HTTP errors

    result = response.json()

    if result['status'] == 'ok':
        print('Answer:', result['data']['answer'])
        print('\nSources:')
        for resource in result['data']['resources']:
            print(f"- {resource['title']}: {resource['link']}")
    else:
        print(f"Error: {result['code']} - {result['message']}")

except requests.exceptions.RequestException as e:
    print(f'Request failed: {e}')

JavaScript (Node.js)

const fetch = require('node-fetch');

const apiKey = process.env.FELO_API_KEY;
const url = 'https://openapi.felo.ai/v2/chat';

const headers = {
  'Authorization': `Bearer ${apiKey}`,
  'Content-Type': 'application/json'
};

const body = JSON.stringify({
  query: 'What are the benefits of renewable energy?'
});

fetch(url, { method: 'POST', headers, body })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    if (data.status === 'ok') {
      console.log('Answer:', data.data.answer);
      console.log('\nSources:');
      data.data.resources.forEach(resource => {
        console.log(`- ${resource.title}: ${resource.link}`);
      });
    } else {
      console.error(`Error: ${data.code} - ${data.message}`);
    }
  })
  .catch(error => {
    console.error('Request failed:', error);
  });

JavaScript (Browser/Fetch API)

const apiKey = 'YOUR_API_KEY'; // In production, get this from your backend

fetch('https://openapi.felo.ai/v2/chat', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: 'What are the benefits of renewable energy?'
  })
})
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    if (data.status === 'ok') {
      console.log('Answer:', data.data.answer);

      // Display sources
      const sourcesList = data.data.resources.map(resource =>
        `<li><a href="${resource.link}">${resource.title}</a></li>`
      ).join('');

      document.getElementById('answer').innerHTML = data.data.answer;
      document.getElementById('sources').innerHTML = sourcesList;
    } else {
      console.error(`Error: ${data.code} - ${data.message}`);
    }
  })
  .catch(error => {
    console.error('Request failed:', error);
  });

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type ChatRequest struct {
    Query string `json:"query"`
}

type ChatResponse struct {
    Status  string `json:"status"`
    Message string `json:"message,omitempty"`
    Data    struct {
        ID            string `json:"id"`
        MessageID     string `json:"message_id"`
        Answer        string `json:"answer"`
        QueryAnalysis struct {
            Queries []string `json:"queries"`
        } `json:"query_analysis"`
        Resources []struct {
            Link    string `json:"link"`
            Title   string `json:"title"`
            Snippet string `json:"snippet"`
        } `json:"resources"`
    } `json:"data,omitempty"`
    Code      string `json:"code,omitempty"`
    RequestID string `json:"request_id,omitempty"`
}

func main() {
    apiKey := os.Getenv("FELO_API_KEY")
    url := "https://openapi.felo.ai/v2/chat"

    requestBody := ChatRequest{
        Query: "What are the benefits of renewable energy?",
    }

    jsonData, err := json.Marshal(requestBody)
    if err != nil {
        fmt.Printf("Error marshaling request: %v\n", err)
        return
    }

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        fmt.Printf("Error creating request: %v\n", err)
        return
    }

    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("Error making request: %v\n", err)
        return
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error reading response: %v\n", err)
        return
    }

    var chatResp ChatResponse
    err = json.Unmarshal(body, &chatResp)
    if err != nil {
        fmt.Printf("Error unmarshaling response: %v\n", err)
        return
    }

    if chatResp.Status == "ok" {
        fmt.Println("Answer:", chatResp.Data.Answer)
        fmt.Println("\nSources:")
        for _, resource := range chatResp.Data.Resources {
            fmt.Printf("- %s: %s\n", resource.Title, resource.Link)
        }
    } else {
        fmt.Printf("Error: %s - %s\n", chatResp.Code, chatResp.Message)
    }
}

Rate Limiting

The /v2/chat endpoint enforces rate limits on three dimensions:

  • Per API Key - limits requests made with a single API Key within a 1-minute window.
  • Per User (minute) - limits the aggregate requests across all API Keys owned by a user within a 1-minute window.
  • Per User (hour) - limits the aggregate requests across all API Keys owned by a user within a 1-hour window.

Any dimension being exceeded triggers a 429 Too Many Requests response.

The exact numeric limits may change over time. Always read them from the response headers below, not from documentation snapshots.

Response headers (every response, including 429):

HeaderMeaning
X-RateLimit-LimitTighter of the two dimension limits currently in effect
X-RateLimit-RemainingMinimum remaining quota across both dimensions
X-RateLimit-ResetUnix timestamp (seconds) when the current window resets

On 429, additionally:

HeaderMeaning
Retry-AfterRecommended retry delay (seconds)

Example 429 body (the message reflects the current configured limits at request time):

{
  "status": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests. Limit: <N>/min per API key, <M>/min per user, <H>/hour per user.",
  "request_id": "..."
}

Recommended client behavior:

  1. Read X-RateLimit-Remaining on every success response; back off proactively as it approaches 0.
  2. On 429, wait at least Retry-After seconds (or until X-RateLimit-Reset) before retrying.
  3. Do not hardcode limit values - they may be tuned without API contract changes.

If you need higher rate limits, contact [email protected].

Best Practices

1. Handle Errors Gracefully

Always check the response status and handle errors appropriately:

if result['status'] == 'ok':
    # Process successful response
    answer = result['data']['answer']
else:
    # Handle error
    error_code = result['code']
    error_message = result['message']
    # Log error, show user-friendly message, etc.

2. Implement Retry Logic

For transient errors (5xx status codes), implement exponential backoff:

import time

max_retries = 3
retry_delay = 1  # seconds

for attempt in range(max_retries):
    response = requests.post(url, headers=headers, json=data)

    if response.status_code < 500:
        break  # Success or client error, don't retry

    if attempt < max_retries - 1:
        time.sleep(retry_delay * (2 ** attempt))  # Exponential backoff

3. Cache Responses

Consider caching responses for identical queries to reduce API calls and improve response times.

4. Validate Input

Validate user input before sending to the API:

  • Ensure query is not empty
  • Check query length (1-2000 characters)
  • Sanitize input to prevent injection attacks

5. Monitor Usage

Track your API usage to stay within rate limits and optimize costs:

  • Log request counts and response times
  • Monitor error rates
  • Set up alerts for unusual patterns

Support

Need help with the Chat API?

Changelog

v2.0.0 (Current)

  • Initial release of Chat API
  • Support for natural language queries
  • Real-time web search integration
  • AI-generated answers with source citations