Documentation
Documentation

Overview

Spore docs

Start here

Getting startedInstall the CLIAuthentication and keys

Build

Search and filtersSegments and the loopEnrichment lifecycle

Recipes

Recipe: search to a live SegmentRecipe: enrich without a segmentRecipe: land your own research

Connect

Events

Account

Credits and billing

Reference

Conventions
API reference

Search and filters

The company filter AST, the field vocabulary that defines it, and how canonical hashing makes an identical search free for 24 hours.

The vocabulary comes first

GET /api/v1/catalog?section=filters is the grammar for everything on this page: every filterable field with its type, the operator ids it accepts, closed enum domains where they exist, and the rule parameters it takes. Operator definitions are listed once under operators and referenced by id.

It is projected from the same filter schema the server validates and evaluates against, so nothing it lists is unsupported and nothing it omits will be accepted. Free, cacheable, and safe to poll.

curl https://www.spore.ee/api/v1/catalog?section=filters \
  -H "Authorization: Bearer $SPORE_API_KEY"

The filter AST

A filter is a tree of two node kinds:

  • a group — { "type": "group", "logic": "AND" | "OR", "children": [...] }
  • a rule — { "type": "rule", "fieldId": ..., "operator": ..., "value": ... }

Groups nest, so any boolean combination is expressible. fieldId and operator must both come from the catalog.

Searching

POST /api/v1/search runs the AST against the live company population:

{
	"filter": {
		"type": "group",
		"logic": "AND",
		"children": [
			{
				"type": "rule",
				"fieldId": "countryCode",
				"operator": "in",
				"value": ["EE"]
			}
		]
	},
	"sortBy": "revenue",
	"sortOrder": "desc",
	"maxCredits": 5
}

The response has three parts:

  • count — how many Legal entities matched.
  • analytics — the full market-intelligence aggregate over every match, not just the sample.
  • sample — up to 100 Legal entities, capped server-side, minted as Companies of your organization and unlocked from birth. Each row is a Company with its live registry section, exactly as GET /api/v1/companies/{companyId} returns it.

The response also carries the immutable searchResultId, filterHash, newlyUnlocked, alreadyUnlocked, charged, balance, replayed, and expiresAt fields. newlyUnlocked and alreadyUnlocked are opaque Company ids.

The cap is deliberate. Aggregate intelligence over an entire population is what search is for; the full roster leaves search through the receipt's own door, POST /api/v1/search/{searchId}/unlock, which mints and unlocks every match at one credit per Legal entity you have not unlocked before. See Segments and the loop.

The canonical hashes

Every response carries filterHash: a SHA-256 over a canonical serialization of the filter in which object keys are sorted. Two ASTs that mean the same thing therefore hash the same, whatever order you wrote their keys in.

Array order is still significant — ["EE", "LV"] and ["LV", "EE"] are different serializations, so normalize your own lists if you want them to dedupe.

The 24-hour replay identity is a separate request hash over normalized filter, sortBy, sortOrder, and sampleSize. Changing the sort or sample size is a distinct search even when filterHash is unchanged. The returned searchResultId is the receipt for that exact request.

Charging and free replay

A search charges max(5, newly unlocked sample ids), never more than its quote, max(5, sampleSize), which maxCredits and the key's spend bounds are checked against. "dryRun": true answers { "dryRun": true, "quote": … } and nothing else: it reads no registry data, mints nothing, and charges nothing. Repeating the identical request within 24 hours is free. The response says which happened:

FieldMeaning
chargedcredits actually deducted — 0 on a replay
replayedtrue when the 24-hour request identity matched an unexpired receipt
balancethe organization's balance after the call

The window is per organization, not per key: a search one key paid for is free for every other key in the organization until the receipt expires.

Errors

A 422 means the filter itself was refused. The top-level error is lowercase, such as unknown_field, unsupported_operator, missing_param, invalid_value, or invalid_filter; filter refusals also carry field and, when applicable, operator at the top level. A 402 max_credits_exceeded refusal carries requiredCredits and maxCredits, so raise the ceiling and retry. A 503 means the company search backend is unavailable; every other status, and the envelope they all share, is in Conventions.

Authentication and keys

Bearer authentication, exact capability and Operation access, expiration, rolling budgets, and key delegation.

Segments and the loop

Name a live filter over your Companies, read its members, refine it, and use what you learn.

On this page

The vocabulary comes firstThe filter ASTSearchingThe canonical hashesCharging and free replayErrors