BETA

QuickStart Guide

Get started with the BoloDoc API in minutes. This guide walks you through authentication and making your first API calls.

To use the BoloDoc API, you'll first need to sign up. It's free.

Step 1: Get Your API Key

Your API key is required to authenticate all API requests. You received it via email when you registered.

How to Access Your API Key:

  • Check your registration email - Your API key was sent when you signed up
  • Reset it - Go to your Profile page and use the "Reset API Key" button
Security Warning: Never commit your API key to version control or share it publicly. Store it securely in environment variables or a key vault. If compromised, reset it immediately from your profile page.
Step 2: Get an Access Token

Exchange your API key for a JWT access token. Tokens expire after 60 minutes.

Request:

POST https://www.bolodoc.io/v1/auth/token
Content-Type: application/json

{
    "api_key": "your-api-key-here"
}

Code Examples:

// Using fetch API
const API_KEY = process.env.BOLO_API_KEY; // Store securely
const BASE_URL = "https://www.bolodoc.io";

async function getAccessToken() {
    const response = await fetch(`${BASE_URL}/v1/auth/token`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            api_key: API_KEY
        })
    });
    
    if (response.ok) {
        const data = await response.json();
        console.log('Access token:', data.access_token);
        console.log('Expires in:', data.expires_in, 'seconds');
        console.log('Your role:', data.role);
        return data.access_token;
    } else {
        const error = await response.text();
        throw new Error(`Failed to get token: ${response.status} - ${error}`);
    }
}

// Usage
getAccessToken()
    .then(token => console.log('Ready to make API calls!'))
    .catch(error => console.error(error));
import os
import requests

# Store API key securely in environment variable
API_KEY = os.getenv("BOLO_API_KEY")
BASE_URL = "https://www.bolodoc.io"

def get_access_token():
    response = requests.post(
        f"{BASE_URL}/v1/auth/token",
        json={"api_key": API_KEY}
    )
    
    if response.status_code == 200:
        token_data = response.json()
        print(f"Access token: {token_data['access_token']}")
        print(f"Expires in: {token_data['expires_in']} seconds")
        print(f"Your role: {token_data['role']}")
        return token_data['access_token']
    else:
        raise Exception(f"Failed to get token: {response.status_code} - {response.text}")

# Usage
try:
    access_token = get_access_token()
    print("Ready to make API calls!")
except Exception as e:
    print(f"Error: {e}")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

public class BoloAuth {
    private static final String API_KEY = System.getenv("BOLO_API_KEY");
    private static final String BASE_URL = "https://www.bolodoc.io";
    private static final HttpClient client = HttpClient.newHttpClient();
    private static final Gson gson = new Gson();
    
    public static String getAccessToken() throws Exception {
        JsonObject requestBody = new JsonObject();
        requestBody.addProperty("api_key", API_KEY);
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/v1/auth/token"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(requestBody)))
            .build();
        
        HttpResponse response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() == 200) {
            JsonObject data = gson.fromJson(response.body(), JsonObject.class);
            String accessToken = data.get("access_token").getAsString();
            int expiresIn = data.get("expires_in").getAsInt();
            String role = data.get("role").getAsString();
            
            System.out.println("Access token: " + accessToken);
            System.out.println("Expires in: " + expiresIn + " seconds");
            System.out.println("Your role: " + role);
            
            return accessToken;
        } else {
            throw new Exception("Failed to get token: " + 
                response.statusCode() + " - " + response.body());
        }
    }
    
    public static void main(String[] args) {
        try {
            String token = getAccessToken();
            System.out.println("Ready to make API calls!");
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class BoloAuth
{
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("BOLO_API_KEY");
    private static readonly string BaseUrl = "https://www.bolodoc.io";
    private static readonly HttpClient client = new HttpClient();
    
    public static async Task GetAccessToken()
    {
        var requestBody = new { api_key = ApiKey };
        var json = JsonSerializer.Serialize(requestBody);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        var response = await client.PostAsync($"{BaseUrl}/v1/auth/token", content);
        
        if (response.IsSuccessStatusCode)
        {
            var responseBody = await response.Content.ReadAsStringAsync();
            var data = JsonSerializer.Deserialize(responseBody);
            
            var accessToken = data.GetProperty("access_token").GetString();
            var expiresIn = data.GetProperty("expires_in").GetInt32();
            var role = data.GetProperty("role").GetString();
            
            Console.WriteLine($"Access token: {accessToken}");
            Console.WriteLine($"Expires in: {expiresIn} seconds");
            Console.WriteLine($"Your role: {role}");
            
            return accessToken;
        }
        else
        {
            var error = await response.Content.ReadAsStringAsync();
            throw new Exception($"Failed to get token: {response.StatusCode} - {error}");
        }
    }
    
    public static async Task Main()
    {
        try
        {
            var token = await GetAccessToken();
            Console.WriteLine("Ready to make API calls!");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Error: {e.Message}");
        }
    }
}
package main

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

const baseURL = "https://www.bolodoc.io"

type TokenRequest struct {
    APIKey string `json:"api_key"`
}

type TokenResponse struct {
    AccessToken string `json:"access_token"`
    TokenType   string `json:"token_type"`
    ExpiresIn   int    `json:"expires_in"`
    Role        string `json:"role"`
}

func getAccessToken() (string, error) {
    apiKey := os.Getenv("BOLO_API_KEY")
    
    requestBody := TokenRequest{APIKey: apiKey}
    jsonData, err := json.Marshal(requestBody)
    if err != nil {
        return "", err
    }
    
    resp, err := http.Post(
        baseURL+"/v1/auth/token",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    
    if resp.StatusCode == 200 {
        var tokenResp TokenResponse
        err = json.Unmarshal(body, &tokenResp)
        if err != nil {
            return "", err
        }
        
        fmt.Printf("Access token: %s\n", tokenResp.AccessToken)
        fmt.Printf("Expires in: %d seconds\n", tokenResp.ExpiresIn)
        fmt.Printf("Your role: %s\n", tokenResp.Role)
        
        return tokenResp.AccessToken, nil
    }
    
    return "", fmt.Errorf("failed to get token: %d - %s", resp.StatusCode, string(body))
}

func main() {
    token, err := getAccessToken()
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Println("Ready to make API calls!")
}

Response:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "bearer",
    "expires_in": 3600,
    "role": "public",
    "redirect_url": "/v1/auth/profile"
}
Step 3: Try A Simple Search

Use the /v1/search/simple endpoint for wildcard-based searches. Available to all authenticated users.

Request:

POST https://www.bolodoc.io/v1/search/simple
Authorization: Bearer {your-access-token}
Content-Type: application/json

{
    "filters": [
        {"field": "title", "value": "Murder*"}
    ],
    "logic": "AND",
    "limit": 25
}

Request Parameters:

Parameter Type Required Default Description
filters array Yes - One or more filter objects. Each object requires a field name and a value string with optional * wildcard characters.
logic string No AND How to combine multiple filters when more than one is provided. AND returns only records that satisfy every filter. OR returns records that satisfy at least one filter.
limit integer No 25 Maximum number of records to return. Accepted values: 25, 50, 100, 250, 500, 5000. Your subscription tier enforces a ceiling: Basic and Premium Monthly are capped at 25 regardless of the value you submit. Premium Annual subscribers may request up to 5000.
rules string No strict Controls how the API handles string values that contain no * wildcard. See the explanation below.

The rules Parameter: strict vs flex

The rules parameter controls whether the API automatically expands string field values that contain no wildcard character.

Mode Behavior when no wildcard is present Input value Effective match type
strict (default) No expansion occurs. A bare value with no * is treated as a case-insensitive exact match. "Male" sex equals "Male" exactly
flex String fields without any wildcard are automatically wrapped as *value*, producing a contains match. Integer and date fields are never affected by flex mode. "murder" title contains "murder" (same as supplying *murder*)

Use strict when precision matters and you are already placing wildcards exactly where needed. Use flex when you want broad contains-style matching without typing * on every filter value.

Request Body Examples:

Example 1: Single filter, starts-with wildcard

Trailing * matches any title beginning with "Murder". logic and limit are omitted, so both default values apply (AND and 25 respectively).

{
  "filters": [
    {
      "field": "title",
      "value": "Murder*"
    }
  ]
}

Example 2: Multiple filters with AND logic

Both conditions must be true. The sex filter has no wildcard, so in strict mode (the default) it is an exact match. The title filter uses *...* for a contains match.

{
  "filters": [
    {
      "field": "sex",
      "value": "Male"
    },
    {
      "field": "title",
      "value": "*murder*"
    }
  ],
  "logic": "AND",
  "limit": 25
}

Example 3: Multiple filters with OR logic

A record is returned if it matches either filter. Useful for searching across multiple subject categories or aliases in a single call.

{
  "filters": [
    {
      "field": "title",
      "value": "*robbery*"
    },
    {
      "field": "title",
      "value": "*kidnapping*"
    }
  ],
  "logic": "OR",
  "limit": 50
}

Example 4: Array field search (languages, field_offices, subjects)

Array fields store multiple values per record. A bare value with no wildcard in strict mode checks whether any element in the array exactly matches the value. Field office names must be lowercase and joined (e.g., losangeles).

{
  "filters": [
    {
      "field": "languages",
      "value": "Spanish"
    },
    {
      "field": "field_offices",
      "value": "miami"
    }
  ],
  "logic": "AND",
  "limit": 25
}

Example 5: flex mode — contains matching without explicit wildcards

Setting rules to flex automatically wraps string field values that contain no * as *value*, producing a contains match. Here both caution and description values are expanded automatically. Integer and date fields are never affected by flex mode.

{
  "filters": [
    {
      "field": "caution",
      "value": "armed"
    },
    {
      "field": "description",
      "value": "dangerous"
    }
  ],
  "logic": "OR",
  "rules": "flex",
  "limit": 25
}
Step 4: Try An Advanced Search PREMIUM

The /v1/search/advanced endpoint is available to Premium subscribers. It supports explicit comparison operators, numeric and date range queries, and grouped conditions with independent AND/OR logic both within and between groups.

Request Structure:

POST https://www.bolodoc.io/v1/search/advanced
Authorization: Bearer {your-access-token}
Content-Type: application/json

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "title", "operator": "contains", "value": "murder"},
                {"field": "reward_max", "operator": "gte", "value": 10000}
            ]
        }
    ],
    "group_logic": "AND",
    "limit": 25
}

Request Parameters:

Parameter Type Required Default Description
groups array Yes - One or more filter group objects. Each group contains a condition and a rules array. At least one group with at least one rule is required.
groups[].condition string Yes - How to combine the rules within that specific group. AND means all rules in the group must match. OR means at least one rule in the group must match.
groups[].rules array Yes - One or more rule objects. Each rule requires a field, an operator, and a value. For the between operator, value must be an array of exactly two values in [min, max] order.
group_logic string No AND How to combine the results of multiple groups. AND requires all groups to match. OR requires at least one group to match. Only meaningful when two or more groups are provided.
limit integer No 25 Maximum records to return. Accepted values: 25, 50, 100, 250, 500, 5000. Your subscription tier caps the maximum: Premium Monthly is capped at 25; Premium Annual may request up to 5000.

How Groups and Conditions Interact

Advanced search applies logic at two levels. The condition field inside each group controls how its own rules are evaluated. The top-level group_logic field controls how the evaluated result of each group is combined against the others.

// Logical structure:
// (Group 1 result) [group_logic] (Group 2 result)
//
// Group 1 result = rule_A [condition] rule_B [condition] rule_C
// Group 2 result = rule_D [condition] rule_E
//
// Example with group_logic=OR, condition=AND in each group:
// (title contains "murder" AND sex = "Male") OR (reward_max >= 100000)

Request Body Examples:

Example 1: String fields — contains, equals, ends_with (single AND group)

All three rules must be true. Demonstrates three string operators in one group. contains and ends_with are case-insensitive substring matches; equals is a case-insensitive exact match.

{
  "group_logic": "AND",
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "title",
          "operator": "contains",
          "value": "murder"
        },
        {
          "field": "sex",
          "operator": "equals",
          "value": "Male"
        },
        {
          "field": "title",
          "operator": "ends_with",
          "value": "Jr"
        }
      ]
    }
  ],
  "limit": 25
}

Example 2: Numeric fields — gte, lte, between (integer operators)

Demonstrates all three forms of numeric range filtering. The first rule uses two separate rules to bracket an age range; the second rule uses between as a shorthand for the same pattern on reward. between accepts an array of exactly two integers in [min, max] order and is inclusive on both ends.

{
  "group_logic": "AND",
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "age_min",
          "operator": "gte",
          "value": 30
        },
        {
          "field": "age_max",
          "operator": "lte",
          "value": 50
        },
        {
          "field": "reward_max",
          "operator": "between",
          "value": [10000, 100000]
        }
      ]
    }
  ],
  "limit": 25
}

Example 3: Timestamp fields — gt and between (date operators)

Date values must be strings in YYYY-MM-DD format. gt returns records modified strictly after the given date. between on a date field is inclusive on both ends and covers the full calendar day for each boundary.

{
  "group_logic": "AND",
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "publication",
          "operator": "gt",
          "value": "2020-01-01"
        },
        {
          "field": "modified",
          "operator": "between",
          "value": ["2024-01-01", "2024-12-31"]
        }
      ]
    }
  ],
  "limit": 25
}

Example 4: Array fields — contains and equals (text array operators)

Array fields (subjects, languages, field_offices, possible_states, etc.) store multiple values per record. The contains operator checks whether any element in the array matches the substring; equals checks for an exact element match. Field office values must be lowercase and joined (e.g., losangeles). State values use ISO 3166-2 format (US-CA) but the API also accepts the full state name and normalizes it.

{
  "group_logic": "AND",
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Kidnapping"
        },
        {
          "field": "languages",
          "operator": "equals",
          "value": "Spanish"
        },
        {
          "field": "field_offices",
          "operator": "equals",
          "value": "miami"
        },
        {
          "field": "possible_states",
          "operator": "equals",
          "value": "US-FL"
        }
      ]
    }
  ],
  "limit": 25
}

Example 5: OR condition within a single group

Setting condition to OR inside a group means a record is returned if it satisfies any of the rules. Useful when you want to match multiple possible values for the same field without creating separate groups.

{
  "group_logic": "AND",
  "groups": [
    {
      "condition": "OR",
      "rules": [
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Murder"
        },
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Assault"
        },
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Robbery"
        }
      ]
    }
  ],
  "limit": 50
}

Example 6: Multiple groups with OR group_logic

Each group is evaluated independently using its own condition. The group_logic then combines those results. Here, a record is returned if it satisfies Group 1 entirely (a high-value male fugitive) OR Group 2 entirely (any terrorism subject with a recent publication date). This is the mechanism for expressing compound boolean logic that cannot be represented in a single flat list of rules.

{
  "group_logic": "OR",
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "sex",
          "operator": "equals",
          "value": "Male"
        },
        {
          "field": "reward_max",
          "operator": "gte",
          "value": 100000
        }
      ]
    },
    {
      "condition": "AND",
      "rules": [
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Terrorism"
        },
        {
          "field": "publication",
          "operator": "gte",
          "value": "2015-01-01"
        }
      ]
    }
  ],
  "limit": 50
}
Searchable Fields Reference

Both Simple and Advanced search endpoints accept the following fields. Use the exact field names shown below.

String Fields

Field Name Description
titlePerson's name or case title
descriptionBrief description of the person
detailsDetailed case information
sexGender (Male, Female)
raceRace/ethnicity (cleaned)
race_rawRace/ethnicity (original FBI data)
nationalityCountry of citizenship
place_of_birthBirth location
hairHair color (cleaned)
hair_rawHair color (original FBI data)
eyesEye color (cleaned)
eyes_rawEye color (original FBI data)
buildBody build description
complexionSkin complexion
weightWeight description (text)
scars_and_marksIdentifying marks, tattoos, scars
cautionWarning information
warning_messageSafety warnings
remarksAdditional notes
reward_textReward description text
statusCase status
ncicNCIC number
pathFBI URL path
pathidFBI path identifier
urlFull FBI URL
poster_urlWanted poster image URL
person_classificationPerson type classification
poster_classificationPoster type classification

Integer Fields

Field Name Description
age_minMinimum estimated age
age_maxMaximum estimated age
height_minMinimum height (inches)
height_maxMaximum height (inches)
weight_minMinimum weight (pounds)
weight_maxMaximum weight (pounds)
reward_minMinimum reward amount (USD)
reward_maxMaximum reward amount (USD)

Timestamp Fields

Field Name Description
modifiedLast modified date in FBI database
publicationOriginal publication date

Array Fields

Field Name Description
aliasesKnown aliases and alternate names
dates_of_birth_usedKnown dates of birth used
field_offices FBI field offices handling the case show values
languages Languages spoken show values
locationsKnown locations
occupationsKnown occupations
possible_countries Countries where person may be located (ISO 3166 codes) show values
possible_states US states where person may be located show values
subjectsCrime categories/subjects
Search Operators Reference

Simple Search (Wildcard Patterns)

Simple search uses wildcard patterns with the * character:

Pattern Behavior Example
*text*Contains{"field": "title", "value": "*murder*"}
text*Starts with{"field": "title", "value": "John*"}
*textEnds with{"field": "title", "value": "*Smith"}
textExact match{"field": "sex", "value": "Male"}

Advanced Search (Explicit Operators)

Advanced search uses explicit operator names for precise control:

Text Operators

Operator Description Example
equalsExact match (case-insensitive){"field": "sex", "operator": "equals", "value": "Male"}
containsContains substring{"field": "title", "operator": "contains", "value": "murder"}
starts_withStarts with text{"field": "title", "operator": "starts_with", "value": "John"}
ends_withEnds with text{"field": "title", "operator": "ends_with", "value": "Smith"}

Numeric/Date Operators

Operator Description Example
equalsEqual to{"field": "age_min", "operator": "equals", "value": 30}
gtGreater than{"field": "reward_max", "operator": "gt", "value": 10000}
ltLess than{"field": "age_max", "operator": "lt", "value": 50}
gteGreater than or equal{"field": "reward_min", "operator": "gte", "value": 5000}
lteLess than or equal{"field": "height_max", "operator": "lte", "value": 72}
betweenBetween two values{"field": "reward_max", "operator": "between", "value": [5000, 50000]}
Response Formats PREMIUM

Search endpoints support multiple response formats via the format query parameter.

Format Access Description
jsonBasic, PremiumDefault JSON format with full metadata
csvPremiumComma-separated values for spreadsheet import
parquetPremiumColumnar format for cloud-based data lake architectures
txtPremiumHuman-readable plain text BOLO format
xmlPremiumStructured XML for RMS/CAD system integration

Usage:

Append the format query parameter to the endpoint URL. The request body is unchanged — only the URL changes.

POST https://www.bolodoc.io/v1/search/simple?format=csv
Authorization: Bearer {your-access-token}
Content-Type: application/json

{
  "filters": [
    {
      "field": "title",
      "value": "*murder*"
    }
  ],
  "limit": 250
}

The same pattern applies to the advanced endpoint:

POST https://www.bolodoc.io/v1/search/advanced?format=xml
Authorization: Bearer {your-access-token}
Content-Type: application/json

{
  "groups": [
    {
      "condition": "AND",
      "rules": [
        {
          "field": "subjects",
          "operator": "contains",
          "value": "Terrorism"
        }
      ]
    }
  ],
  "limit": 100
}
Use Case Search Examples

The following examples cover a wide range of real-world scenarios. Each shows the full request body. All string comparisons are case-insensitive unless noted. Simple search examples use POST /v1/search/simple. Advanced search examples use POST /v1/search/advanced and require a Premium subscription.

Simple Search Examples

Use Case: Find all subjects whose name contains the word "murder"

Using *murder* pattern to get a contains match on the title field.

{
    "filters": [
        {"field": "title", "value": "*murder*"}
    ],
    "limit": 25
}

Use Case: Find subjects whose name starts with "John"

Trailing * produces a starts-with match.

{
    "filters": [
        {"field": "title", "value": "John*"}
    ],
    "limit": 25
}

Use Case: Find subjects whose name ends with "Garcia"

Leading * produces an ends-with match.

{
    "filters": [
        {"field": "title", "value": "*Garcia"}
    ],
    "limit": 25
}

Use Case: Find all female subjects

No wildcard on an exact field value. rules defaults to strict, so "Female" is an exact match.

{
    "filters": [
        {"field": "sex", "value": "Female"}
    ],
    "limit": 25
}

Use Case: Find female subjects whose title contains "bank"

Multiple filters combined with AND (the default). Both conditions must be true for a record to appear.

{
    "filters": [
        {"field": "sex",   "value": "Female"},
        {"field": "title", "value": "*bank*"}
    ],
    "logic": "AND",
    "limit": 25
}

Use Case: Find subjects wanted for robbery OR kidnapping in the title

Using logic: "OR" so that a record matching either filter is returned.

{
    "filters": [
        {"field": "title", "value": "*robbery*"},
        {"field": "title", "value": "*kidnapping*"}
    ],
    "logic": "OR",
    "limit": 50
}

Use Case: Find subjects with any caution warning mentioning weapons

Searching the caution field which contains FBI-issued safety warnings.

{
    "filters": [
        {"field": "caution", "value": "*weapon*"}
    ],
    "limit": 25
}

Use Case: Find subjects who speak Spanish

The languages field is an array. A contains match checks whether any element in the array matches.

{
    "filters": [
        {"field": "languages", "value": "Spanish"}
    ],
    "limit": 25
}

Use Case: Find subjects handled by the Miami field office

Field office values are lowercase and single-word. "miami" is the correct format.

{
    "filters": [
        {"field": "field_offices", "value": "miami"}
    ],
    "limit": 25
}

Use Case: Find subjects possibly located in California

The possible_states field uses ISO 3166-2 format. You may supply the full state name and the API normalizes it automatically.

{
    "filters": [
        {"field": "possible_states", "value": "California"}
    ],
    "limit": 25
}

Use Case: Find subjects possibly located in California OR Texas

OR logic across two state filters. Both US-CA and US-TX formats are accepted, or you can supply the full name.

{
    "filters": [
        {"field": "possible_states", "value": "California"},
        {"field": "possible_states", "value": "Texas"}
    ],
    "logic": "OR",
    "limit": 50
}

Use Case: Find subjects possibly located in Mexico

The possible_countries field uses ISO 3166 alpha-3 codes, but the API accepts country names and normalizes them.

{
    "filters": [
        {"field": "possible_countries", "value": "Mexico"}
    ],
    "limit": 25
}

Use Case: Broad keyword search without typing wildcards (flex mode)

Setting rules: "flex" automatically wraps string values without wildcards as a contains match. The input "armed" becomes *armed* for string fields. Integer and date fields are unaffected.

{
    "filters": [
        {"field": "caution",     "value": "armed"},
        {"field": "description", "value": "dangerous"}
    ],
    "logic": "OR",
    "rules": "flex",
    "limit": 25
}

Use Case: Exact match with strict mode explicitly declared

Declaring rules: "strict" (the default) ensures "White Collar Crime" is an exact match on the subjects field, not a contains search.

{
    "filters": [
        {"field": "subjects", "value": "White Collar Crime"}
    ],
    "rules": "strict",
    "limit": 25
}

Use Case: Get up to 100 results for a broad name search (Premium Annual required)

The limit is 100. Basic subscribers are capped at 25 regardless of this value. Only Premium Annual subscribers may retrieve up to 5000.

{
    "filters": [
        {"field": "title", "value": "Jose*"}
    ],
    "limit": 100
}

Use Case: Find subjects with tattoo or scar descriptions mentioning a specific body part

Searching the scars_and_marks field for identifying feature references.

{
    "filters": [
        {"field": "scars_and_marks", "value": "*neck*"}
    ],
    "limit": 25
}

Use Case: Find subjects known to use the alias "El Chapo"

The aliases field is an array of known names. A contains match scans all alias entries.

{
    "filters": [
        {"field": "aliases", "value": "*Chapo*"}
    ],
    "limit": 25
}

Advanced Search Examples PREMIUM

Use Case: Find subjects with rewards over $50,000

Single group, single rule using the gte operator on a numeric field.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "reward_max", "operator": "gte", "value": 50000}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find male subjects wanted for murder with a reward of at least $10,000

Three rules in one group, all joined by AND. Every condition must be satisfied.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "sex",        "operator": "equals",   "value": "Male"},
                {"field": "title",      "operator": "contains", "value": "murder"},
                {"field": "reward_max", "operator": "gte",      "value": 10000}
            ]
        }
    ],
    "group_logic": "AND",
    "limit": 25
}

Use Case: Find subjects in an estimated age range of 25 to 40

Using two rules on the same group to bracket a numeric range. Alternatively, the between operator can be used on a single rule.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "age_min", "operator": "gte", "value": 25},
                {"field": "age_max", "operator": "lte", "value": 40}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects whose reward falls between $5,000 and $100,000

The between operator accepts an array of exactly two values: [min, max]. Both endpoints are inclusive.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "reward_max", "operator": "between", "value": [5000, 100000]}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects whose records were modified in a specific date range

Date values must be in YYYY-MM-DD format. The between operator is inclusive on both ends for date fields.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "modified", "operator": "between", "value": ["2024-01-01", "2024-12-31"]}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects whose records were added or modified after a specific date

Using gt (greater than) on the publication date field.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "publication", "operator": "gt", "value": "2023-06-01"}
            ]
        }
    ],
    "limit": 50
}

Use Case: Find subjects with specific physical dimensions

Height is stored in inches. This query finds subjects estimated between 5'6" (66 in) and 6'0" (72 in) tall.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "height_min", "operator": "gte", "value": 66},
                {"field": "height_max", "operator": "lte", "value": 72}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects wanted for kidnapping handled by the Miami or Los Angeles offices

OR condition within a single group. The subject must be wanted for kidnapping AND (handled by miami OR losangeles). Note: to express that logic, use two separate groups with OR group_logic, because a single group with mixed AND/OR is not supported.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "subjects",      "operator": "contains", "value": "Kidnapping"},
                {"field": "field_offices", "operator": "equals",   "value": "miami"}
            ]
        },
        {
            "condition": "AND",
            "rules": [
                {"field": "subjects",      "operator": "contains", "value": "Kidnapping"},
                {"field": "field_offices", "operator": "equals",   "value": "losangeles"}
            ]
        }
    ],
    "group_logic": "OR",
    "limit": 25
}

Use Case: Find subjects who match either a high-reward profile OR a terrorism-related category

Two groups joined by OR group_logic. A record matching either group will be returned.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "reward_max", "operator": "gte", "value": 100000}
            ]
        },
        {
            "condition": "AND",
            "rules": [
                {"field": "subjects", "operator": "contains", "value": "Terrorism"}
            ]
        }
    ],
    "group_logic": "OR",
    "limit": 50
}

Use Case: Find male subjects over 40 who speak Spanish and may be in Mexico or Venezuela

A multi-condition group combining demographic, language, and location filters. All conditions are joined by AND.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "sex",                "operator": "equals",   "value": "Male"},
                {"field": "age_min",            "operator": "gte",      "value": 40},
                {"field": "languages",          "operator": "contains", "value": "Spanish"},
                {"field": "possible_countries", "operator": "equals",   "value": "MEX"}
            ]
        },
        {
            "condition": "AND",
            "rules": [
                {"field": "sex",                "operator": "equals",   "value": "Male"},
                {"field": "age_min",            "operator": "gte",      "value": 40},
                {"field": "languages",          "operator": "contains", "value": "Spanish"},
                {"field": "possible_countries", "operator": "equals",   "value": "VEN"}
            ]
        }
    ],
    "group_logic": "OR",
    "limit": 25
}

Use Case: Find subjects with white collar crime ties and no reward (reward_max equals 0)

Combining a category filter with a numeric equals check. Useful to identify cases where no reward has been posted.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "subjects",   "operator": "contains", "value": "White Collar Crime"},
                {"field": "reward_max", "operator": "equals",   "value": 0}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects whose title starts with "John" and are shorter than 5'8" (68 inches)

Mixing a string operator (starts_with) with a numeric operator (lt) in a single group.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "title",      "operator": "starts_with", "value": "John"},
                {"field": "height_max", "operator": "lt",          "value": 68}
            ]
        }
    ],
    "limit": 25
}

Use Case: Find subjects wanted for cyber crime with records modified in 2025

Combining a subjects array contains check with a date range, using a single AND group.

{
    "groups": [
        {
            "condition": "AND",
            "rules": [
                {"field": "subjects",  "operator": "contains", "value": "Cyber"},
                {"field": "modified",  "operator": "gte",      "value": "2025-01-01"},
                {"field": "modified",  "operator": "lte",      "value": "2025-12-31"}
            ]
        }
    ],
    "limit": 25
}

Use Case: Export up to 500 records for subjects wanted for violent crimes (Premium Annual, CSV format)

Requesting a higher limit and appending ?format=csv to the endpoint URL. Only Premium Annual subscribers may retrieve up to 500 or 5000 records. The format query parameter is appended to the URL, not the request body.

POST https://www.bolodoc.io/v1/search/advanced?format=csv

{
    "groups": [
        {
            "condition": "OR",
            "rules": [
                {"field": "subjects", "operator": "contains", "value": "Murder"},
                {"field": "subjects", "operator": "contains", "value": "Assault"},
                {"field": "subjects", "operator": "contains", "value": "Robbery"}
            ]
        }
    ],
    "limit": 500
}
JSON Reponse Body For Searches
The JSON response body containing all the fields and values from searches is large. Run the examples in the Swagger docs for /v1/search/simple and /v1/search/advanced to view the JSON response structure.
Rate Limits & Best Practices

Token Limits:

  • Tokens expire after 60 minutes
  • Request a new token before expiration

Result Limits by Subscription:

Plan Max Results Data Quality Features
Basic 25 per search Raw data Simple search, JSON only
Premium (Monthly) 25 per search Cleaned data Simple + Advanced search, All formats
Premium (Annual) 5,000 per search Cleaned data All features + Category endpoints + Document archive

Best Practices:

  • Cache tokens and reuse them until expiration
  • Handle rate limit errors (429 status code) gracefully
  • Use specific filters to narrow results
  • Implement exponential backoff for retries
  • Store your API key securely (environment variables, key vault)
  • Never hardcode API keys in your source code
Next Steps