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
Requires Python 3.11 or later.
# Core client: raw GraphQL plus every prompt strategy except AgentStrategy
pip install enigma-api-client
# AgentStrategy (Claude Agent SDK on Amazon Bedrock)
pip install "enigma-api-client[anthropic]"
# BedrockProvider (Claude on Amazon Bedrock)
pip install "enigma-api-client[bedrock]"
# Both of the above
pip install "enigma-api-client[all]"
Quick Start
The client supports both raw GraphQL queries and natural language queries.
- The example below sends a raw GraphQL query.
- Later examples show 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:
# Send 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
Clientholds 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 callawait 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 sends the prompt to Enigma's hosted query generation service and authenticates with your Enigma API key, so client.search(prompt="...") works with no extra setup. To generate queries locally instead, pass a TemplateStrategy, GenerativeStrategy, or AgentStrategy via ClientConfig.query_strategy.
TemplateStrategy and GenerativeStrategy call Anthropic by default (the ANTHROPIC_API_KEY environment variable, or anthropic_api_key=); pass llm_provider= to use OpenRouter or Amazon Bedrock instead (see LLM providers below). AgentStrategy runs on Amazon Bedrock with AWS credentials and requires the claude_agent_sdk package (pip install "enigma-api-client[anthropic]"). ServiceStrategy runs generation server side and needs no LLM key.
Service Strategy (default)
Sends your prompt to Enigma's hosted query generation service over HTTPS, and query generation runs server side. It uses the template strategy by default. To choose another, construct one with your key, because a ServiceStrategy you create does not inherit the Client's key (without enigma_api_key= it reads ENIGMA_API_KEY): query_strategy=ServiceStrategy(query_strategy="generative", enigma_api_key="your-api-key"), imported from enigma_api_client.strategies.
import asyncio
from enigma_api_client import Client, ClientConfig
async def main():
# ServiceStrategy is used when no query_strategy is configured
async with Client(ClientConfig(enigma_api_key="your-api-key")) as client:
# 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 to classify the prompt into one of 11 query templates (or into one of two refusal categories: category discovery, and prompts that name no business, person, or address) and to extract parameters, then assembles the GraphQL query deterministically from the matching template. Fast and reliable, with no hallucinated field names, because the GraphQL comes from tested templates. Two more templates join the menu when they apply: address lookups when address search is on (see Address lookups below), and card transactions when the strategy is called directly with an entitled tier (await strategy.generate_query(prompt, tier=AccessTier.EXTERNAL); Client.search always generates at the default tier, so it never offers this template).
import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import TemplateStrategy
async def main():
config = ClientConfig(enigma_api_key="your-api-key", query_strategy=TemplateStrategy())
async with Client(config) as client:
# 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, address lookups (what is at a street address), 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 to generate the full GraphQL query and variables directly. The LLM receives an excerpt of the bundled Enigma GraphQL skill reference plus fixed query patterns and guardrails as context. More flexible than TemplateStrategy for complex or unusual queries that don't fit the templates, but slower, and it carries some risk of hallucinated field names. Generated queries are validated against the API schema and repaired (deterministically where possible, otherwise with one more LLM call) before they are returned.
import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import GenerativeStrategy
async def main():
config = ClientConfig(enigma_api_key="your-api-key", query_strategy=GenerativeStrategy())
async with Client(config) as client:
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 through an agent that can read the full enigma-graphql skill, including the field whitelist, examples, field corrections, and pagination templates. The agent reasons about the query and refines its output, and the strategy validates the answer against the schema before returning it. It produces the highest quality queries for complex scenarios but is the slowest option.
The agent runs on Amazon Bedrock only. Credentials come from the standard AWS credential chain, and the identity needs bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream on the inference profile and on the foundation model in every region the profile routes to (default model us.anthropic.claude-haiku-4-5-20251001-v1:0; pass model= and region= to change it, and keep the region inside the profile's geography). Its one tool reads files inside the bundled plugin: it has no shell, file writes, web access, or file attachments, it runs with every inherited environment variable blanked except process basics, TLS and proxy settings and the AWS credential chain, and it keeps no session transcript on disk. Its answer is written inline (variables become literals typed against the tier's schema) and passes the same post-draft gates and checks as GenerativeStrategy, with the deterministic repair but no model repair pass. max_turns (default 8) and max_budget_usd (default 0.25) bound each call.
Requires: pip install "enigma-api-client[anthropic]"
import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import AgentStrategy
async def main():
config = ClientConfig(enigma_api_key="your-api-key", query_strategy=AgentStrategy())
async with Client(config) as client:
response = await client.search(
prompt="Find all open Dunkin' Donuts locations in Massachusetts with phone numbers"
)
print(response.data)
asyncio.run(main())
Address lookups
TemplateStrategy and GenerativeStrategy answer "what is at this address" prompts: a street address with a house number and no business name, such as "What is at 100 Main St, Wichita, KS?" or "Who is registered at 200 Oak Ave, Floor 8, Springfield, IL?". The query is an entityType: ADDRESS search limited to 10 results. It returns the matched building and its units (up to 10 rows); when the prompt names a unit, that unit usually comes first. Each result carries the address fields, five counts (associatedBrandCount, operatingLocationOperatesAtCount, legalEntityReceivesMailAtCount, registrationRecordedCount, associatedPersonCount), and the operating locations (with brand names), legal entities, and registrations at the address.
- A business name plus an address ("the Starbucks at 123 Main St") stays an operating location lookup.
- A ZIP code alone, a street with no house number, or a business type on a street ("the barbershop on Elm Street") raises
EnigmaClientExceptionwithUNSUPPORTED_DATA_REQUESTED. - Before the query is sent, the SDK cleans the address the model extracted. It requires a house number, moves a suite, floor, or apartment into
street2in USPS short form (STE 1A,FL 8), drops a unit designator with no number, removes surrounding prose and a trailing country, and converts state names to their postal codes (such asNY).GenerativeStrategy.generate_query()andAgentStrategy.generate_query()includeaddress-inputindeterministic_repairfor a cleaned query. TemplateStrategy,GenerativeStrategy, andAgentStrategynever return a query that selectsbrandViaLegalEntityCount,brandViaPersonLegalEntityCount, orlegalEntityViaRegistrationCount.GenerativeStrategyandAgentStrategyremove them when they can and includedeferred-fieldindeterministic_repair; otherwiseGenerativeStrategyrepairs the query with the model, andAgentStrategyraisesEnigmaClientExceptionwithUNSUPPORTED_DATA_REQUESTED(orUnrepairedQueryErrorwhen a repair would land on one or the answer does not parse).- Turn it on or off with
address_search=onTemplateStrategy,GenerativeStrategy, orAgentStrategy(True,False, or"auto"), or with theENIGMA_ADDRESS_SEARCHenvironment variable (on,off, orauto; unset or unrecognized meansauto). The setting is read when the strategy is constructed. - In
automode, each strategy instance decides at its first prompt for an access tier whether the API schema supports address search, and keeps that answer, even one from a failed schema request, for the life of the instance.Clientsupplies the schema for the default tier. With no schema (noClientcreated in the process and no introspector registered throughenigma_api_client.validation.set_schema_introspector, or a schema request that failed at that first prompt),autostays off. - With address search off, a prompt that gives only an address is refused; on
TemplateStrategythe reason isbare address lookup. ServiceStrategydoesn't take this setting.AgentStrategytakes it likeGenerativeStrategy: its answer's ADDRESS searches pass the same address gate.
import asyncio
from enigma_api_client import Client, ClientConfig
from enigma_api_client.strategies import TemplateStrategy
async def main():
config = ClientConfig(enigma_api_key="your-api-key", query_strategy=TemplateStrategy(address_search=True))
async with Client(config) as client:
response = await client.search(prompt="What is at 1600 Pennsylvania Ave NW, Washington DC?")
for address in response.data["search"]:
print(address["fullAddress"], address["associatedBrandCount"])
asyncio.run(main())
Strategy comparison
| Strategy | Approach | Speed | Quality | Extra deps |
|---|---|---|---|---|
ServiceStrategy (default) |
Delegates generation to Enigma's hosted service | network bound | Server managed, always current | None |
TemplateStrategy |
LLM classifies, then a deterministic template build | about 1 to 3 s | Reliable, no hallucination | None |
GenerativeStrategy |
LLM generates the full GraphQL end to end | about 3 to 10 s | Flexible, some hallucination risk | None |
AgentStrategy |
Claude agent on Amazon Bedrock that reads the skill | about 10 to 60 s | 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 LLM key. Best default for most callers.
- TemplateStrategy: Best when you want deterministic local generation. Covers the common query patterns with 12 templates and no 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 templates. The LLM generates custom GraphQL from the API reference and fixed patterns. 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.
LLM providers
TemplateStrategy and GenerativeStrategy take any LLM provider through llm_provider=. Without one they use AnthropicProvider with anthropic_api_key= or ANTHROPIC_API_KEY; the strategy's model= argument applies only to that default provider.
from enigma_api_client.strategies import (
AnthropicProvider, BedrockProvider, GenerativeStrategy, OpenRouterProvider, TemplateStrategy,
)
# Anthropic Messages API (ANTHROPIC_API_KEY)
TemplateStrategy(llm_provider=AnthropicProvider(model="claude-haiku-4-5-20251001"))
# OpenRouter (OPENROUTER_API_KEY). provider_routing is sent as OpenRouter's "provider"
# object and reasoning as its "reasoning" object; omit either to keep the default.
GenerativeStrategy(llm_provider=OpenRouterProvider(
model="anthropic/claude-haiku-4.5",
provider_routing={"order": ["anthropic"], "allow_fallbacks": False},
reasoning={"enabled": False},
))
# Claude on Amazon Bedrock (pip install "enigma-api-client[bedrock]"), signed with ambient AWS credentials.
# The default model is a US inference profile; pass model= outside US regions.
TemplateStrategy(llm_provider=BedrockProvider(region="us-east-1"))
A custom provider implements the LlmProvider protocol: async def complete(system, user, max_tokens) -> Completion. Raise ValueError for a missing key or bad configuration (reported as GENERATE_QUERY_CALLBACK_NOT_CONFIGURED) and RuntimeError for a provider failure (reported as TRANSIENT_PROVIDER_ERROR); any other exception propagates unwrapped.
Environment variables
| Variable | Used by | Meaning |
|---|---|---|
ENIGMA_API_KEY |
ServiceStrategy |
ServiceStrategy's key when enigma_api_key= is not passed (Client passes its own key only to the default ServiceStrategy it creates) |
ANTHROPIC_API_KEY |
AnthropicProvider |
Anthropic API key |
OPENROUTER_API_KEY |
OpenRouterProvider |
OpenRouter API key |
AWS_REGION, AWS_DEFAULT_REGION |
BedrockProvider, AgentStrategy |
Region when region= is not passed (default us-east-1) |
ENIGMA_SHAPE_CACHE_ENABLED |
TemplateStrategy, GenerativeStrategy |
0, false, no, or off disables the query shape cache |
ENIGMA_ADDRESS_SEARCH |
TemplateStrategy, GenerativeStrategy, AgentStrategy |
on, off, or auto (see Address lookups above) |
Query shape cache
TemplateStrategy and GenerativeStrategy keep an in-process cache of query shapes learned from successful generations. Once a shape has been confirmed across several different entities, a matching prompt is served from the cache without an LLM call. The cache is on by default and is shared by every caller of the same strategy instance at the same tier. Disable it with shape_cache=False or ENIGMA_SHAPE_CACHE_ENABLED=0.
Errors and response fields
Generation failures raise EnigmaClientException; its error_code is an EnigmaClientErrorCode:
| Code | Meaning |
|---|---|
INVALID_QUERY |
The prompt or the generated query is unusable (includes the two subclasses below) |
UNSUPPORTED_DATA_REQUESTED |
Enigma does not serve what the prompt asks for |
GENERATE_QUERY_CALLBACK_NOT_CONFIGURED |
A missing key or a provider configuration fault |
TRANSIENT_PROVIDER_ERROR |
The LLM provider timed out, rate limited, or failed with a 5xx; retry with backoff |
PROVIDER_ERROR |
Any other provider failure (for example 401 or 403); retrying will not help |
RESTRICTED_DATA_REQUESTED and INVALID_VARIABLES exist for callers that enforce their own policies; the SDK does not raise them.
The provider codes come from TemplateStrategy and GenerativeStrategy. ServiceStrategy reports any hosted service failure (any status other than 200, including 401, 429, and 5xx, or a GraphQL error) as INVALID_QUERY, and its network errors (timeouts, connection failures) propagate as httpx exceptions, as do network errors from Client.search itself.
Both exceptions below import from enigma_api_client.
TruncatedOutputError: the model hit its output token cap. This repeats for the same model and prompt, so do not retry with the same model.UnrepairedQueryError:GenerativeStrategyproduced a query that stayed invalid after repair. It carries.draft,.errors, and.tier, and you can pass it to anotherGenerativeStrategyasgenerate_query(prompt, prior_attempt=exc)to start from that draft. Its message quotes values from the generated query, so log the exception type anderror_coderather thanstr(exc).
SearchResponse.unavailable_data lists the parts of a prompt Enigma cannot serve, as {"field", "reason", "message"} entries (reason is contractual or out_of_scope), while the rest of the prompt is still answered.
Calling a strategy directly (await strategy.generate_query(prompt)) returns a GenerateQueryResponse with query, variables, unavailable_data, and diagnostics such as cache_rule (set when the shape cache took part) and deterministic_repair (the SDK's rewrite kinds, when it repaired the query without a model call). After a call, enigma_api_client.strategies.GENERATION_USAGE.get() returns that call's token counts (LlmUsage) and GENERATION_STAGE.get() its last generation stage; read them in the same task that awaited the call. ServiceStrategy reports zero usage; AgentStrategy reports its run's totals.
Logging
The SDK's logging surface is split by level according to content. Raw model text (the
agent transcript, the prose or non JSON draft echoes, the validator detail behind
a failed repair) logs only at DEBUG on the emitting module's logger
(enigma_api_client.*); enabling DEBUG on those loggers (or globally) writes
prompts and model output to your log sink, so do not enable it in environments
with data retention constraints. WARNING and INFO lines are fixed templates
carrying counts, statuses, rule and kind vocabularies, and file paths, with no
raw model or validator text.
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,
)
hasNextPageis 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.endCursoris the cursor for the next page.
The flag is off by default. With it off, responses are unchanged byte for byte, 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 }
}
}
}
}"""
async def all_location_ids(client):
cursor, ids = None, []
while True:
response = await client.search(query=query, variables={"cursor": cursor})
connection = response.data["search"][0]["operatingLocations"]
ids += [edge["node"]["id"] for edge in connection["edges"]]
page = connection["pageInfo"]
if not page["hasNextPage"]:
return ids
cursor = page["endCursor"]
Keep the connection's conditions and orderBy fixed across a run, since cursors are bound to the arguments that minted them.
roles does not paginate. The schema advertises last, after, and before on roles, but the API rejects all three at execution (HTTP 400). Fetch roles as a single page with first: N, optionally narrowed with conditions.
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 about 50 field names, 5 searchable entity types (BRAND, OPERATING_LOCATION, LEGAL_ENTITY, PERSON, ADDRESS), 10+ filter operators, and 6 connection patterns. Many field names are unintuitive, and LLMs consistently guess wrong:
| LLMs guess | Actual field name |
|---|---|
status |
operatingStatus |
url |
website |
description |
industryDesc |
title |
jobTitle |
averageRating |
reviewScoreAvg |
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:
searchreturns a flat list, not a connection. LLMs writesearch { edges { node { ... } } }; the correct form issearch { ... on Brand { ... } }.- Every
searchneeds a retrieval handle. AsearchInputwithoutname,website,person, orphoneNumberis schema valid but rejected at execution with HTTP 400 "Search must include name, website, person, or phone_number". Filters never retrieve. An address alone retrieves only asentityType: ADDRESSwithaddress.street1(a street line with a house number) oraddress.id(see Address lookups above);idandtinlookups also resolve an entity directly. LegalEntity.personsis almost always empty. To find officers, traverseregisteredEntities, thenregistrations, thenroles, thenlegalEntities. LLMs take the direct (empty) path.- Searching
OPERATING_LOCATIONby brand name returns nothing. SearchBRANDand filter itsoperatingLocationsconnection. LLMs don't know this. - Do not use
prompton SearchInput. The field passes schema validation, but the API rejects any query that combinespromptwith a selection set (HTTP 400 at execution). Retrieve byname,website, orpersoninstead. There is no semantic discovery mode:name: "coffee shop"returns nothing useful, and category counts go throughaggregate. totalCountdoes not exist on connections. Requesting it causes HTTP 400. Useaggregatequeries instead.- Cursor pagination uses one query, not two. A single query with
$cursor: Stringandafter: $cursorpaginates every page. Passcursor: nullon page 1 (null starts from the beginning; omitting the declared variable is what causes HTTP 400 "Undefined variable"). Keep the connection'sconditionsandorderByfixed across a run, since cursors are bound to the arguments that minted them.
Rules enforced at execution, not in the schema
Some constraints are not expressed in the GraphQL schema: a query can pass schema validation (or any introspection based tooling) and still be rejected when it executes. Known rules include:
| Rule | Rejection |
|---|---|
search requires a retrieval handle: name, website, person, or phoneNumber (id and tin lookups also resolve directly). Filters never retrieve; an address alone retrieves only as entityType: ADDRESS with address.street1 or address.id. |
Search must include name, website, person, or phone_number |
prompt cannot be combined with a selection set; avoid the field entirely. |
'prompt' cannot be combined with a selection set |
roles supports first and conditions only, even though the schema advertises cursor arguments. |
LegalEntity.roles does not currently support last, after or before (the error names the parent type) |
A PERSON search retrieves by person: { firstName, lastName } (or id / tin), not by name. |
Person search must include an identifier or first, middle or last name |
Filter AND, OR, and NOT take a list of clauses, and NOT takes exactly one. |
Rejected at execution |
A filter clause carries exactly one operator key; combine several under one AND list. |
Rejected at execution |
IS_NULL and IS_NOT_NULL test a column. On a connection path (such as registrations), test for related rows with HAS or NOT: [{ HAS: [...] }]. |
Rejected at execution |
An aggregate filter on brands.industries.industryCode needs { EQ: ["brands.industries.industryType", "naics_2022_code"] } in the same AND. |
Joins aren't allowed for filter |
GenerativeStrategy checks these rules offline (ess_runtime_rule_violations) and repairs or refuses before returning a query; this also covers ServiceStrategy calls that run the generative strategy server side. The prompt= argument to client.search() is the natural language request, unrelated to SearchInput.prompt. Other strategies and raw GraphQL callers should conform to the rules directly.
Data quality traps
Even syntactically valid queries can return silently wrong data:
- Franchise resolution: Searching
BRANDfor a local franchise (for example, a single Days Inn) returns the parent chain's data: 1,969 locations rolled up instead of the single franchisee. websiteContentsis a connection, not a scalar. UsewebsiteContents(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, and so on). The GraphQL is assembled from 12 hand written, tested templates (plus the card and address templates when they apply). No hallucination risk, but limited to the patterns those templates cover.
GenerativeStrategy sends an excerpt of the skill reference plus fixed query patterns and guardrails as system prompt context, validates the result against the schema and the rules above, and repairs it when needed. This covers most pitfalls, but the LLM can still pick the wrong fields or miss structural rules, especially for unusual queries. Review generated queries before using them in production.
AgentStrategy has full access to the skill files, examples, and field corrections reference, and can refine queries iteratively. Highest quality, but slowest and most expensive. The strategy validates the agent's answer against the schema and raises UnrepairedQueryError when it fails.
Recommendations
- For production automation, use
TemplateStrategyor raw GraphQL. The templates cover the most common query patterns and are fixed, tested queries. - For prototyping or one off complex queries, use
GenerativeStrategyorAgentStrategy, but always review the generated query before trusting the results. - Always check
response.is_success()and inspectresponse.errorsbefore usingresponse.data. A plausible looking query can still fail at the API level.
Upgrading from 0.8.x
AgentStrategyruns on Amazon Bedrock only and takes the keyword argumentsmodel,region,max_turns, andmax_budget_usd;anthropic_api_keyis removed. The calling identity needsbedrock:InvokeModelandbedrock:InvokeModelWithResponseStreamon the model.AgentStrategyraises instead of returning an empty query:UnrepairedQueryErrorwhen its answer fails schema validation or does not parse,UNSUPPORTED_DATA_REQUESTEDfrom the refusal gatesGenerativeStrategyapplies,INVALID_QUERYfor an empty prompt or answer (the "not applicable" refusal), a run that ends without a query, an answer off its schema, a mutation or subscription, an answer whose variables do not fit their declared types, one nested too deeply, or one a check could not read, andTRANSIENT_PROVIDER_ERRORorPROVIDER_ERRORwhen the model call or the CLI fails.AgentStrategyrecords its token usage inGENERATION_USAGE.- The
anthropicandallextras pinclaude_agent_sdk==0.2.110. AgentStrategyserves its answer inline with{}variables, refuses the call before the agent runs when the tier has no schema (PROVIDER_ERROR), refuses a mutation or subscription (INVALID_QUERY), sets nounavailable_data, and takesaddress_search=likeGenerativeStrategy.
Upgrading from 0.7.11 or earlier
- Custom
LlmProvider:complete()now returnsenigma_api_client.strategies.Completion(text, truncated, input_tokens=..., cache_read_tokens=..., cache_write_tokens=..., output_tokens=...)instead ofstr. - Custom
QueryStrategy:generate_query(prompt, *, tier=DEFAULT_TIER, prior_attempt=None)gainedprior_attempt. Implementations must setGENERATION_USAGEto a freshLlmUsage()on entry. GenerativeStrategyraisesTruncatedOutputErrorandUnrepairedQueryError. Both subclassEnigmaClientExceptionwith codeINVALID_QUERY, so existing handlers still catch them.- LLM provider transport failures now raise
EnigmaClientException:TRANSIENT_PROVIDER_ERRORfor timeouts and connection errors,GENERATE_QUERY_CALLBACK_NOT_CONFIGUREDfor configuration faults. They used to surface as rawhttpxexceptions. restricted_fields_forandgate_blocked_fieldsno longer listisMentionedInMediaContents, and now listheadcounts,corporateFamilies, andcustomsfor theEXTERNALandREDISTRIBUTIONtiers.- Address lookups are new and default to
auto(see Address lookups above). WithENIGMA_ADDRESS_SEARCH=off, a prompt that gives only an address raisesUNSUPPORTED_DATA_REQUESTED; 0.7.11'sTemplateStrategysent it as a nameless operating location search that the API rejected with HTTP 400. GenerativeStrategynow refuses some drafts withUNSUPPORTED_DATA_REQUESTEDinstead of serving them: a person search by surname alone, an EIN or filing number placed inname, a wildcard name pattern, a street address placed inname, and a category count offered for a listing request.AgentStrategylogs its transcript at DEBUG onenigma_api_client.strategies.agent, not at INFO on the root logger.TemplateStrategyrefuses a blank prompt withINVALID_QUERYbefore any LLM call.
Metadata
Release files for enigma-api-client 0.9.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| enigma_api_client-0.9.2-py3-none-any.whl | Python 3 | none | any | Details for enigma_api_client-0.9.2-py3-none-any.whl |
Release files / enigma_api_client-0.9.2-py3-none-any.whl
| Download URL | enigma_api_client-0.9.2-py3-none-any.whl |
|---|---|
| Size | 281.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d25201005515d6f29533b972145631b59efe0bf44137dcec25aff7191e543d05
|
|
BLAKE2b-256 checksum How to use checksums |
a2d86dcb964db3c177896306814cd62fba6c02e3e895ad518410ce1a5ae221fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Security for enigma-api-client 0.9.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.