Skip to main content

Enigma's Public API SDK

Project description

Enigma API Client

Python SDK for the Enigma GraphQL business intelligence API. Query Enigma's database of 250M+ businesses using raw GraphQL or natural language prompts.

Installation

# Core client (raw GraphQL + all prompt strategies except AgentStrategy)
pip install enigma-api-client

# With Claude agent (skill-based generation, requires Anthropic API key)
pip install enigma-api-client[anthropic]

Quick Start

The client supports both raw GraphQL queries and natural language queries.

  • Below example showcases the raw GraphQL approach.
  • Subsequent examples show how to use the natural language strategies.

Using GraphQL Queries

import asyncio
from enigma_api_client import Client, ClientConfig

async def main():
    config = ClientConfig(enigma_api_key="your-api-key")
    async with Client(config) as client:
        # Example, sending off a handwritten GraphQL query
        response = await client.search(
            query="""query {
              search(searchInput: { name: "Starbucks", entityType: BRAND }) {
                ... on Brand {
                  id
                  names(first: 1) { edges { node { name } } }
                }
              }
            }"""
        )
        print(response.data)

asyncio.run(main())

Connection lifecycle. A Client holds reusable keep-alive connections for the GraphQL endpoint and the query strategy. Reuse one client across requests and release it when done — use it as an async context manager (async with Client(config) as client:, shown above) or call await client.aclose().

Natural Language Query strategies

The client supports four strategies for turning natural language prompts into GraphQL queries. By default it uses ServiceStrategy, which delegates generation to Enigma's Semantic Transformation Service, so client.search(prompt="...") works with no extra setup. To generate queries locally instead, pass a TemplateStrategy, GenerativeStrategy, or AgentStrategy via ClientConfig.query_strategy.

The local strategies (TemplateStrategy, GenerativeStrategy, AgentStrategy) require the ANTHROPIC_API_KEY environment variable. ServiceStrategy runs generation server-side and does not. AgentStrategy additionally requires the claude_agent_sdk package (pip install enigma-api-client[anthropic]).

Service Strategy (default)

Sends your prompt to Enigma's Semantic Transformation Service over HTTPS, and query generation runs server-side. It uses the template strategy by default; pass query_strategy="generative" or query_strategy="agent" to ServiceStrategy to choose another.

import asyncio
from enigma_api_client import Client, ClientConfig

client = Client(ClientConfig(
    enigma_api_key="your-api-key",
    # will use ServiceStrategy by default
))

async def main():
    # Brand lookup
    response = await client.search(prompt="Tell me about Chipotle")
    print(response.data)

    # Location search
    response = await client.search(prompt="Find Starbucks locations in California")
    print(response.data)

    # Person search
    response = await client.search(prompt="Find companies owned by Elon Musk")
    print(response.data)

    # KYB verification
    response = await client.search(prompt="Verify legal entity for Enigma Technologies in NY")
    print(response.data)

asyncio.run(main())

TemplateStrategy — LLM-classified, template-built

Uses an LLM (via Anthropic Messages API) to classify the user's prompt into one of 12 pre-defined query templates and extract parameters, then assembles the GraphQL query deterministically from the matching template. Fast and reliable — no hallucinated field names since the GraphQL is built from verified templates.

import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import TemplateStrategy

client = Client(ClientConfig(
    enigma_api_key="your-api-key",
    query_strategy=TemplateStrategy(),
))

async def main():
    # Brand lookup
    response = await client.search(prompt="Tell me about Chipotle")
    print(response.data)

    # Location search
    response = await client.search(prompt="Find Starbucks locations in California")
    print(response.data)

    # Person search
    response = await client.search(prompt="Find companies owned by Elon Musk")
    print(response.data)

    # KYB verification
    response = await client.search(prompt="Verify legal entity for Enigma Technologies in NY")
    print(response.data)

asyncio.run(main())

The template strategy covers query patterns such as: brand profiles, brand lookups by website, locations, person search, corporate officers, legal entity verification, contacts, watchlist screening, operating location profiles, aggregate counts, and risk assessment. Prompts that don't map to a supported template (for example, open-ended category discovery like "list coffee shops in Denver") raise an EnigmaClientException.

GenerativeStrategy — end-to-end LLM query generation

Uses a single LLM call (via Anthropic Messages API) to generate the full GraphQL query and variables directly. The LLM receives the complete Enigma GraphQL API skill reference as context. More flexible than TemplateStrategy for complex or unusual queries that don't fit neatly into pre-defined templates, but slower and carries some risk of hallucinated field names.

import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import GenerativeStrategy

client = Client(ClientConfig(
    enigma_api_key="your-api-key",
    query_strategy=GenerativeStrategy(),
))

async def main():
    response = await client.search(
        prompt="Find all open Dunkin' Donuts locations in Massachusetts with phone numbers"
    )
    print(response.data)

asyncio.run(main())

AgentStrategy — Claude Agent SDK with skill

Uses the Claude Agent SDK (claude_agent_sdk) to generate GraphQL queries via an interactive agent that has access to the full enigma-graphql skill — including the field whitelist, examples, field corrections, and pagination templates. The agent can read skill files, reason about the query, and iteratively refine its output. Produces the highest quality queries for complex scenarios but is the slowest option.

Requires: pip install enigma-api-client[anthropic]

import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import AgentStrategy

client = Client(ClientConfig(
    enigma_api_key="your-api-key",
    query_strategy=AgentStrategy(),
))

async def main():
    response = await client.search(
        prompt="Find all open Dunkin' Donuts locations in Massachusetts with phone numbers"
    )
    print(response.data)

asyncio.run(main())

Strategy comparison

Strategy Approach Speed Quality Extra deps
ServiceStrategy (default) Delegates generation to Enigma's server network-bound Server-managed, always current None
TemplateStrategy LLM classifies → deterministic template build ~1-3s Reliable, no hallucination None
GenerativeStrategy LLM generates full GraphQL end-to-end ~3-10s Flexible, some hallucination risk None
AgentStrategy Claude agent with skill + tool access ~10-60s Highest quality, iterative refinement claude_agent_sdk

When to use which:

  • ServiceStrategy (default): Generation runs on Enigma's servers, so you get the latest query logic with no local model calls and no ANTHROPIC_API_KEY. Best default for most callers.
  • TemplateStrategy: Best when you want deterministic local generation. Covers 12 common query patterns with zero hallucination risk. The LLM only classifies and extracts parameters, and the GraphQL is deterministic.
  • GenerativeStrategy: Best for complex or unusual queries that don't fit the 12 templates. The LLM sees the full API reference and generates custom GraphQL. Good for prototyping and exploratory queries.
  • AgentStrategy: Best for maximum quality when latency is not a concern. The agent can read examples, check field corrections, and reason about the query. Good for batch processing or one-off complex queries.

Pagination

Connections (any field returning edges { node { ... } }) return at most one page at a time. Getting a full page back (for example 100 rows) does not tell you whether more exist. You need pageInfo for that.

Automatic page-info injection

Set auto_inject_page_info=True on the client and it rewrites every outgoing query, adding pageInfo { hasNextPage endCursor } to every connection that does not already request it. This applies to both raw GraphQL and prompt-generated queries:

config = ClientConfig(
    enigma_api_key="your-api-key",
    auto_inject_page_info=True,
)
  • hasNextPage is an authoritative "is there more?" flag from the backend. Use it instead of inferring truncation from a full page of results. It stays correct even if the page size changes, so there is nothing to keep in sync on your end.
  • endCursor is the cursor for the next page.

The flag is off by default. With it off, responses are byte-for-byte unchanged, so upgrading without setting it changes nothing.

Fetching the next page

Re-run the same query with after: $cursor on the connection, passing the previous page's endCursor. On the first page pass cursor: null (null starts from the beginning; omitting a declared $cursor variable is what triggers an HTTP 400 "Undefined variable"). A single parameterized query paginates every page, so there is no separate first-page query.

# With auto_inject_page_info=True you can omit the explicit `pageInfo` line below;
# the client adds it for you. It is shown here so the response shape is clear.
query = """query Locations($cursor: String) {
  search(searchInput: { name: "Starbucks", entityType: BRAND }) {
    ... on Brand {
      operatingLocations(first: 100, after: $cursor) {
        edges { node { id } }
        pageInfo { hasNextPage endCursor }
      }
    }
  }
}"""

cursor = None
while True:
    response = await client.search(query=query, variables={"cursor": cursor})
    connection = response.data["search"][0]["operatingLocations"]
    for edge in connection["edges"]:
        ...  # process edge["node"]
    page = connection["pageInfo"]
    if not page["hasNextPage"]:
        break
    cursor = page["endCursor"]

Keep the connection's conditions and orderBy fixed across a run, since cursors are bound to the arguments that minted them.

Caveats: LLM-generated GraphQL

The Enigma GraphQL schema has several non-obvious conventions that make LLM-generated queries unreliable without guardrails. Understanding these is important regardless of which strategy you use.

Why this is hard for LLMs

The Enigma schema has ~50 field names, 4 entity types, 10+ filter operators, and 6 connection patterns. Many field names are unintuitive — LLMs consistently guess wrong:

LLMs guess Actual field name
status operatingStatus
url website
description industryDesc
title jobTitle
averageRating reviewScoreAvg
formationDate formationYear (Int, not Date)

These cause HTTP 400 errors ("Cannot query field") that are silent failures if not handled.

Structural pitfalls

Beyond field names, the graph structure has patterns that LLMs regularly get wrong:

  • search returns a flat list, not a connection. LLMs write search { edges { node { ... } } } — the correct form is search { ... on Brand { ... } }.
  • LegalEntity.persons is almost always empty. To find officers, you must traverse registeredEntities → registrations → roles → legalEntities. LLMs take the direct (empty) path.
  • Searching OPERATING_LOCATION by brand name returns nothing. You must search BRAND and filter its operatingLocations connection. LLMs don't know this.
  • name vs prompt on SearchInput are completely different modes. name does entity resolution ("Starbucks"); prompt does semantic discovery ("coffee shop"). Using name: "coffee shop" returns nothing useful.
  • totalCount does not exist on connections. Requesting it causes HTTP 400. Use aggregate queries instead.
  • Cursor pagination uses one query, not two. A single query with $cursor: String + after: $cursor paginates every page — pass cursor: null on page 1 (null starts from the beginning; omitting the declared variable is what causes HTTP 400 "Undefined variable"). Keep the connection's conditions/orderBy fixed across a run, since cursors are bound to the arguments that minted them.

Data quality traps

Even syntactically valid queries can return silently wrong data:

  • Franchise resolution: Searching BRAND for a local franchise (e.g., a single Days Inn) returns the parent chain's data — 1,969 locations rolled up instead of the single franchisee.
  • websiteContents is a connection, not a scalar. Must use websiteContents(first: 1) { edges { node { httpStatusCode } } }.

How the strategies handle this

TemplateStrategy avoids all of the above. The LLM only classifies the prompt into a template and extracts parameters (company name, state, city, etc.). The actual GraphQL is assembled from 12 hand-written, tested templates. Zero hallucination risk, but limited to the patterns those templates cover.

GenerativeStrategy sends the full API skill reference (~600 lines of documentation, guardrails, and examples) as system prompt context. This covers most pitfalls, but the LLM can still hallucinate field names or miss structural rules — especially for unusual queries. Validate generated queries before using them in production.

AgentStrategy has full access to the skill files, examples, field corrections reference, and can iteratively refine queries. Highest quality, but slowest and most expensive. The agent can introspect the schema if it encounters errors.

Recommendations

  • For production automation, use TemplateStrategy or raw GraphQL. The 12 templates cover the most common query patterns and are guaranteed correct.
  • For prototyping or one-off complex queries, use GenerativeStrategy or AgentStrategy but always review the generated query before trusting the results.
  • Always check response.is_success() and inspect response.errors before using response.data. A plausible-looking query can still fail at the API level.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

enigma_api_client-0.5.3-py3-none-any.whl (255.2 kB view details)

Uploaded Python 3

File details

Details for the file enigma_api_client-0.5.3-py3-none-any.whl.

File metadata

File hashes

Hashes for enigma_api_client-0.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 3a5b1e2774895617a8f55cb767422c6459899a295a18a1a535ab288def7deaeb
MD5 3ba1e184ff032c77f5b18025a13adbae
BLAKE2b-256 468265b83184af154c93550b0cbec8718745511f51c0f462f77f85356496813c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page