Skip to main content
Calseta’s enrichment system is runtime-configurable. Adding a new provider requires zero code changes — you configure it via the REST API or by seeding the database. A single adapter class (DatabaseDrivenProvider) handles all providers.

Two Ways to Add Providers

Via API (Runtime)

Create a provider at runtime using the REST API:
curl -X POST http://localhost:8000/v1/enrichment-providers \
  -H "Authorization: Bearer cai_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "provider_name": "greynoise",
    "display_name": "GreyNoise",
    "supported_indicator_types": ["ip"],
    "http_config": {
      "steps": [{
        "method": "GET",
        "url": "https://api.greynoise.io/v3/community/{{value}}",
        "headers": {
          "key": "{{auth_token}}"
        }
      }]
    },
    "auth_config": {
      "token": "your-greynoise-key"
    },
    "malice_rules": {
      "conditions": [
        { "field": "classification", "operator": "eq", "value": "malicious", "verdict": "Malicious" },
        { "field": "classification", "operator": "eq", "value": "unknown", "verdict": "Suspicious" },
        { "field": "classification", "operator": "eq", "value": "benign", "verdict": "Benign" }
      ]
    },
    "cache_ttl_by_type": {
      "ip": 3600
    }
  }'

Via Database Seed (Builtin)

For providers that ship with Calseta, add a seed entry in app/seed/enrichment_providers.py. This is how the four builtin providers (VirusTotal, AbuseIPDB, Okta, Entra) are defined.

HTTP Config Structure

The http_config defines how Calseta calls the provider’s API. It supports template variables that are resolved at runtime.
{
  "steps": [
    {
      "method": "GET",
      "url": "https://api.example.com/v1/lookup/{{value}}",
      "headers": {
        "Authorization": "Bearer {{auth_token}}",
        "Accept": "application/json"
      },
      "params": {
        "maxAgeInDays": "90"
      }
    }
  ]
}

Template Variables

VariableResolves To
{{value}}The indicator value being enriched
{{type}}The indicator type (ip, domain, etc.)
{{auth_token}}The auth credential from auth_config

Auth Types

TypeDescription
no_authNo authentication required (default)
api_keyAPI key — stored in auth_config, resolved via {{auth_token}} in headers
api_tokenAPI token — same resolution as api_key, named differently for clarity
oauth2_client_credentialsOAuth2 client credentials flow
How you send the credential (header name, format) is controlled by the http_config template — auth_type just tells Calseta whether credentials are needed and how to resolve them. For example, to send an API key in a custom header:
{
  "http_config": {
    "steps": [{
      "headers": { "X-Api-Key": "{{auth_token}}" }
    }]
  },
  "auth_config": { "token": "your-key-here" }
}
Auth credentials are encrypted at rest using the ENCRYPTION_KEY environment variable.

Malice Rules

Configure rules that map provider response fields to malice verdicts:
{
  "conditions": [
    {
      "field": "abuse_confidence_score",
      "operator": "gte",
      "value": 80,
      "verdict": "Malicious"
    },
    {
      "field": "abuse_confidence_score",
      "operator": "gte",
      "value": 25,
      "verdict": "Suspicious"
    }
  ],
  "default_verdict": "Benign"
}

Operators

OperatorDescription
eqEquals
neqNot equals
gteGreater than or equal
lteLess than or equal
inIn list
containsContains substring
Conditions are evaluated in order — the first match wins.

Field Extractions

Define which fields from the raw provider response are surfaced to agents in the extracted object:
curl -X POST http://localhost:8000/v1/enrichment-field-extractions \
  -H "Authorization: Bearer cai_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "provider_name": "greynoise",
    "indicator_type": "ip",
    "source_path": "data.classification",
    "target_key": "classification",
    "value_type": "string",
    "description": "GreyNoise classification: benign, malicious, or unknown"
  }'
FieldDescription
source_pathDot-notation path into the raw response (e.g., data.results.score)
target_keyKey name in the extracted object surfaced to agents
value_typeExpected type: string, int, float, bool, list, dict, any
Use the bulk create endpoint to add multiple extractions at once:
curl -X POST http://localhost:8000/v1/enrichment-field-extractions/bulk \
  -H "Authorization: Bearer cai_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "extractions": [
      { "provider_name": "greynoise", "indicator_type": "ip", "source_path": "data.classification", "target_key": "classification", "value_type": "string" },
      { "provider_name": "greynoise", "indicator_type": "ip", "source_path": "data.noise", "target_key": "is_noise", "value_type": "bool" }
    ]
  }'
You can list, update, and delete field extractions via the full CRUD API. Builtin provider extractions are system-managed — they can be toggled on/off but not deleted. When a custom provider is deleted, all its field extractions are automatically removed.

Cache Configuration

Set TTLs per indicator type:
{
  "cache_ttl_by_type": {
    "ip": 3600,
    "domain": 21600,
    "hash_sha256": 86400
  }
}
Values are in seconds. If not specified for a type, the system default applies.

Testing Your Provider

After adding a provider, test with on-demand enrichment:
curl -X POST http://localhost:8000/v1/enrichments \
  -H "Authorization: Bearer cai_your_api_key" \
  -d '{"type": "ip", "value": "8.8.8.8"}'
Then check the results:
curl http://localhost:8000/v1/enrichments/ip/8.8.8.8 \
  -H "Authorization: Bearer cai_your_api_key"

Disabling a Provider

Deactivate a provider without deleting it:
curl -X PATCH http://localhost:8000/v1/enrichment-providers/{uuid} \
  -H "Authorization: Bearer cai_your_api_key" \
  -d '{"is_active": false}'
Disabled providers are skipped during enrichment. Existing cached results remain accessible.

Contributing

See Community Integrations for the process of contributing new provider configurations.