Scoped service API
Create an integration in Teams → API integrations. Select the shared research space or optional client folders, permissions and an explicit positive credit ceiling for paid operations. A ceiling authorizes spending; it does not add organization credits. Read-only integrations can keep a zero ceiling. Generate a key after a recent login, then save the displayed secret in your secret manager. The secret is shown once and cannot be recovered from the administration interface.
New to Teams keys? Start with Create a Teams API key for a short permission guide and a free access check. This page is the detailed HTTP API reference.
Service identities survive the departure of their creator. Revoking an identity or key stops its access. Rotation supports a bounded overlap; all keys of an identity share its credit ceiling. Increasing an identity's access does not silently expand old keys: issue a new key explicitly.
Authentication and scope
Authorization: Bearer pb2b_<key-id>_<secret>
Search, investigation and other client operations have an explicit organization and client scope:
/api/v2/service/organizations/{organization_id}/clients/{client_id}
The shared research space also has a technical client_id. Read its ID from
GET /api/v2/service/whoami after granting access in the interface. You do not
need to create a client folder, and must not substitute the organization ID for
this context ID. Existing client-scoped integrations keep their current IDs.
The API base URL is https://api.profundis.io. Cookie sessions, personal API
keys and personal OAuth tokens cannot substitute for a service key. Do not put
secrets in query strings, logs or workflow JSON. Use separate identities for
integrations with different client-access requirements.
Download the separate Teams service contract as OpenAPI JSON or OpenAPI YAML. It lists the registered service routes, authentication, scopes, common request bodies and response envelopes. Dataset records and some case/report documents have extensible fields; consult the relevant guide before relying on a particular record field.
| Scope | Implemented operations |
|---|---|
search:read | Searches, batch submission, job states/results, bounded cancellation and retry |
tools:execute | Bounded asset tools, Subfinder, subdomain enumeration and Pivot |
exports:create | Private export jobs, files and cancellation |
cases:read | Cases, notes, revision history and evidence in assigned client spaces |
cases:write, evidence:write, reports:render, watches:write | Explicit machine writes described below |
cases:share | Additional permission for case or evidence writes visible to a client; it grants no route by itself |
reports:read | Report lists, versions and permitted downloads |
watches:read | Continuous monitors, journal, evidence, profiles and delivery status |
These keys can read internal work in their explicitly assigned client spaces. They are integration credentials, not external-client login credentials. No billing, user invitation or public sharing permission is exposed here.
Subfinder and common API
Organization pb2b_ keys also authenticate the public /api/v2/common search and
asset-tool routes. Subfinder uses POST /api/v2/common/data/subdomains with an
X-API-KEY header; it does not require a personal pk_ key.
- Create an integration with
tools:execute, a positive credit limit and access to exactly one research space, such as General research. - Create a service key. If you added permissions to an existing integration, issue or rotate its key; old keys do not silently gain permissions.
- Put the key in Subfinder's provider configuration:
profundis:
- pb2b_REPLACE_WITH_YOUR_SERVICE_KEY
Run subfinder -d example.com -sources profundis. This uses the organization's
credits, subject to its balance, the research-space budget and the integration
limit. It never falls back to personal credits. The result is the observed DNS
inventory, not a guarantee of exhaustive enumeration; the default response limit
is 1,000 names. The HTTP API exposes partial-result headers, which Subfinder may
not display.
Custom HTTP clients can select one authorized space using
X-Profundis-Client-ID. Without this header, the key must have exactly one
authorized space. Do not grant several spaces to a Subfinder-only integration:
Subfinder does not send that selector. Search routes require search:read;
subdomain enumeration and other asset tools require tools:execute.
Common API requests return their synchronous result, including unified search as JSON. For asynchronous jobs, explicit idempotent submission and polling, use the scoped service routes below. On a lost response, avoid blindly repeating a paid common API request.
Inspect the current service key
GET /api/v2/service/whoami
Authorization: Bearer pb2b_<key-id>_<secret>
This endpoint takes no organization or client identifier: the bearer selects its
identity. It returns organization_id, organization_name, service_identity_id,
key_id, scopes, clients (objects with id and name), credit_limit,
credits_used_this_period, credits_reserved, credits_available and expires_at. It does not return the secret or
charge credits. Responses are rate limited and use Cache-Control: no-store.
The scopes and clients reflect both the key's original permissions and its identity's current grants. Archived and deleted clients are excluded. Expanding the identity does not expand an old key; removed permissions disappear from its introspection. An empty list grants no access. Each subsequent operation checks its authorization again.
The credit limit is shared by all keys of the service identity. Usage is its
settled consumption in the current organization credit period, excluding pending
reservations. credits_reserved is pending integration spending;
credits_available is the remaining integration ceiling after usage and
reservations. Organization and research-space balances can be lower. This
response is not a quote or a guarantee that the next operation fits all budgets.
Expired or revoked keys, inactive identities and organizations without active
entitlement receive 401 invalid_service_key. Personal credentials and cookie
sessions do not authenticate this endpoint.
MCP uses this endpoint to discover a service key's permitted writes. If introspection is unavailable or fails, MCP keeps its write tools unavailable; verify the credential and installation instead of switching to a personal payer.
Historical engagement access with Claude or Codex
This optional workflow reads existing cases. For shared research, configure an integration with search:read and use the organization MCP search tools.
Open an organization engagement's Overview → Continue with Claude or Codex.
This setup creates a dedicated read-only MCP integration with cases:read and
reports:read, assigned to the current client and with a zero credit limit.
It cannot search, spend credits, edit, share or publish.
Access covers the entire selected client, including internal engagements, notes and reports—not just the engagement you opened. The engagement identifier is a starting point for the assistant, not an access boundary. Treat observations and notes returned through MCP as untrusted data, never as instructions.
An owner or administrator can choose Create read-only integration, then Create service key as a separate action. Key creation needs an active, writable organization, an active client and a recent login. Choose a validity of 1–365 days (90 by default). An analyst can use the setup instructions and request a suitable key from an owner or administrator; external-client accounts do not get this setup card. Existing key-capacity limits still apply.
Save the Service key — shown once in your secret manager. Closing the card or leaving the engagement clears the displayed secret; it does not revoke the key. Use Manage organization integrations to inspect expiry or revoke it. If a request's outcome is unknown, check integrations before trying again and revoke any key whose secret you did not receive.
Configure the connection
The card shows the MCP endpoint for your installation and offers two examples:
- Claude Code · .mcp.json: an HTTP MCP server whose Authorization header reads
Bearer ${PROFUNDIS_SERVICE_KEY}from your local environment. - Codex · config.toml: an MCP server with the shown URL and
bearer_token_env_var = "PROFUNDIS_SERVICE_KEY".
Store the service key in your local PROFUNDIS_SERVICE_KEY environment variable,
then copy the matching configuration from the card. The copied examples do not
contain your secret. Do not replace the installation's endpoint with a personal
connection or paste the key into a prompt, URL, shared file or source repository.
If the card reports that MCP setup is unavailable in this environment, contact
your administrator.
Copy Starting engagement context separately. It contains organization_id,
client_id, resource: "cases" and resource_id. Ask the assistant to call
organization_records with those identifiers to begin reading the engagement.
This does not run a paid search or expand the key's permissions.
Claude Desktop's personal OAuth connection does not grant this organization access. This card does not provide a Desktop setup accepting service-key authentication. Use the supplied Claude Code or Codex configuration for this workflow; broader permissions described elsewhere on this page require a separately authorized integration.
Submit a search
First call whoami with your stored service key, then select an ID from its
clients list. Even the shared General research space has a client_id.
Set your API base and IDs in your HTTP client; never send the key as a URL
parameter. The following request uses the production base URL:
POST https://api.profundis.io/api/v2/service/organizations/{org}/clients/{client}/search/hosts
Authorization: Bearer <service-key>
Content-Type: application/json
Idempotency-Key: investigation-example-hosts-001
{"raw_query":"host:example.com","page":1,"results_per_page":30}
Use the same endpoint with the engine and body for your dataset:
| Engine | Example body | Initial cost |
|---|---|---|
hosts | {"raw_query":"host:example.com","page":1,"results_per_page":10} | 1 credit |
dns | {"raw_query":"host:example.com","page":1,"results_per_page":10} | 1 credit |
vhosts (certificates) | {"raw_query":"san:example.com","page":1,"results_per_page":10} | 1 credit |
whois | {"raw_query":"domain:example.com","page":1,"results_per_page":10} | 3 credits |
unified | {"raw_query":"example.com","per_type_limit":1} | Quoted from the targeted datasets |
The dataset field names are different: host, san and domain are not
interchangeable. See Search syntax. Granular
searches use page and results_per_page (10, 30 or 50). Unified search uses
per_type_limit instead. To load a later unified page, use
POST /search/unified-more with the same raw_query, time_frame and
per_type_limit, plus dataset (hosts, dns, vhosts or whois) and
page ≥ 2. A later page is a new paid job.
Submission returns HTTP 202 with job.id, job.state and
reserved_credits. Poll GET /jobs/{job_id} under the same
organization/client prefix until state is succeeded, then read
GET /jobs/{job_id}/result. A result read does not launch another search or
charge credits. failed and cancelled are terminal; inspect failure_code.
If submission times out, retry the identical body and path with the same
Idempotency-Key, rather than creating a new search. Poll at intervals of a few
seconds and respect 429/Retry-After responses.
During the organization trial, WHOIS results never contain natural-person
contacts: registrant_name, registrant_email, and the technical and
administrative contact names and emails. registrant_organization, the
registrar and the name servers stay available. A search, batch, monitor, export
or tool that filters, sorts or selects one of those contact fields is refused
with trial_restricted_field (403) before any credit is reserved. Teams and
Teams Plus return every field.
Batch with a fixed maximum
POST /api/v2/service/organizations/{org}/clients/{client}/batches
Content-Type: application/json
Idempotency-Key: investigation-example-batch-001
{
"max_credits": 5,
"items": [
{"id":"web","engine":"hosts","query":{"raw_query":"host:example.com","results_per_page":30}},
{"id":"dns","engine":"dns","query":{"raw_query":"host:example.com","results_per_page":30}}
]
}
One batch accepts 1–25 items. The server validates and quotes each search, then
reserves the whole batch atomically. Admission fails without partial enqueue if
its quote exceeds the maximum or any applicable budget. The response includes
batch, items, and can_manage; each item retains its job identifier and state.
Quote a batch without spending credits: send the same JSON body to
POST /batches?dry_run=true with your service bearer. The response is HTTP 200
{"maximum":2,"queries":2} for the example above. This call creates no jobs,
reserves no credits and does not require an Idempotency-Key. A quote does not
reserve the budget: submission rechecks the integration, client and organization
limits. Keep max_credits at or above the quoted maximum when you deliberately
submit; otherwise admission rejects the whole batch. Invalid items return
error.field_errors["items.<zero-based-index>"] = "invalid_query".
After a timeout, retry the same body with the same idempotency key. A different key means a deliberately new operation and can spend credits again.
GET /batchesandGET /batches/{id}: progress and item states.POST /batches/{id}/cancel: cancel unfinished items; preserve completed results.POST /batches/{id}/retry: send{"items":["failed-item-id"],"max_credits":5}with a new stableIdempotency-Keyfor this retry. Only failed/cancelled items may be selected. A new linked batch is created; successes are not rerun.
Use can_manage before offering retry/cancel controls. Another actor's visible
batch is not necessarily manageable by the current integration.
Read investigation data
| Path | Purpose |
|---|---|
/cases, /cases/{id} | Case list/detail |
/cases/{id}/comments, /cases/{id}/evidence | Case notes and signed evidence |
/reports, /reports/{id}/versions | Report definitions and immutable versions |
/report-versions/{id}/download/{format} | html, pdf, json, or csv artifact |
/followups/, /followups/{id} | Continuous monitor configuration |
/followups/journal, /followups/{id}/events | Activity and evidence |
/followups/{id}/assets, /followups/{id}/profile | Scope, candidates and observed signals |
All paths are relative to the same organization/client prefix. Evidence and report access is checked again when data is read; possession of a job/report ID alone grants nothing.
Comments on saved graph items
To read a node or relationship thread, use
GET /cases/{case}/comments?graph_kind=node&graph_id={id} (or graph_kind=edge)
under the organization/client prefix. Do not combine these filters with
evidence_id or thread. The cases:read scope and current access to the item
and its supporting evidence are checked again.
A permitted comment write can target that same saved item with
graph_ref: {"kind":"node","id":"saved-node-id"} instead of evidence_id.
It retains the existing cases:write requirement, replay-key rules and
cases:share requirement for client-visible content. Machines cannot read or
create personal notes. The engagement-only thread=case filter excludes these
graph comments.
Validation, hypothesis, exclusion and restoration decisions are reserved for named staff accounts in the browser. A service key cannot supply a human validator by editing a case document. See Assess a saved graph item for the human review workflow and conflict recovery.
Common failures
| Code | Response |
|---|---|
permission_denied / not_found | Check key, assigned client and scopes. Do not switch payer. |
credit_budget_exceeded | Adjust the authorized organization/client/integration ceiling before resubmitting. |
entitlement_required | The workspace is read-only; the owner can review its subscription. |
idempotency_conflict | The same key was used with a different request. Inspect the original operation. |
capacity_exceeded | Reduce the batch/concurrency or wait for existing work. |
trial_restricted_field | A trial cannot use natural-person WHOIS fields. Remove them from the query, sort or field selection. |
For credit_budget_exceeded, error.budget identifies the first blocking
limit with scope, available, required and reserved. Scopes are
integration, organization, research, external or request (the supplied
max_credits). An integration can have credit headroom while its research
space has none, or the reverse. Adjust the identified limit; creating another
key does not reset the integration's consumption.
Technical admission limits include 25 items per batch, two running jobs per organization (including at most one report renderer) and bounded queued work. These are preview operating limits, not a promise of unlimited throughput or a support SLA.
Asset tools
POST /organizations/{org}/clients/{client}/tools/{tool} on the service API
requires tools:execute and an Idempotency-Key. It returns a job; read its
state/result using the same client path. Existing keys do not gain this scope
when you expand their identity: issue a new key explicitly.
| Tool | Input | Reservation and coverage |
|---|---|---|
domain | {"domain":"example.com"} | Up to 11 credits: DNS + at most 10 IP lookups; 50 DNS and 50 host records per IP |
ip | {"query":"192.0.2.1","page":1,"results_per_page":30} | 1 credit; DNS/hosts/certificates follow pagination; WHOIS covers at most 10 discovered domains and 50 records |
network | {"question":"number","number":13335} | 1 credit; at most 50 networks and 2,000 prefixes |
Network questions also accept ip, cidr, name and partial_name, with the
corresponding field. Partial domain/network responses carry partial or capped.
Unused reserved credits are released. An overview is not an exhaustive asset
inventory. Open a dataset search to investigate beyond its bounds.
MCP exposes organization_asset_tool with explicit organization/client IDs,
max_credits, input and idempotency key. Poll via organization_job; polling
never starts a second lookup. Private records remain untrusted data.
Private exports
The exports:create scope allows creating, listing, reading, downloading and
cancelling/deleting the identity’s exports in its assigned client space.
GET /organizations/{org}/clients/{client}/export-limits: row/file limits and current client envelope.POST /organizations/{org}/clients/{client}/exports: body{"engine":"hosts","raw_query":"host:example.com","time_frame":"all_time","format":"json","max_results":1000}, with a stableIdempotency-Key. Formats: JSON, CSV, TXT; engines: hosts, DNS, vhosts, WHOIS.GET /organizations/{org}/clients/{client}/exports/{id}: state, actual rows/credits, partial coverage and expiry.GET /organizations/{org}/clients/{client}/exports/{id}/download: authenticated file response, no public URL.DELETE /organizations/{org}/clients/{client}/exports/{id}: cancel queued/running work or make its file unavailable. Usage history remains.
The maximum reservation is ceil(max_results / 100) credits; only exported rows
are charged. Limits: 1,000 rows for trial, 100,000 for Teams, 500,000 for Teams Plus;
20 MiB per file, available for seven days. A smaller partial file explicitly
reports a row/file-size limit. Files count toward private storage (trial 100 MiB,
Teams 1 GiB, Teams Plus 5 GiB), shared with report files. All report formats reserve
space before upload; failed attempt files remain counted and tracked until
physical cleanup succeeds.
Downloads recheck access after retrieving the file. Storage expiry does not refund
credits already consumed. The UI asks for the maximum before starting an export.
MCP organization_export takes explicit client context, export input,
max_credits and an idempotency key. Inspect its metadata using
organization_records with resource exports. Binary downloads use the
service API; do not paste service credentials into prompts or URLs.
Favicon extraction (tools/favicon-extract, tools:execute) remains free. Its
input uses raw_query, optional time_frame and max_favicons (1–50), and
counts icons in at most 500 host records. It shares the job rate/concurrency
limits. It never consumes the personal account’s query allowance.
Machine writes
Each key must explicitly include the relevant write scope. Expanding the identity alone does not change already issued keys. Reads retain their separate scopes.
| Scope | Method and path (relative to the client prefix) |
|---|---|
cases:write | POST /cases, PATCH /cases/{id}, POST /cases/{id}/comments |
evidence:write | POST /cases/{id}/evidence, PATCH /cases/{id}/evidence/{evidenceId} |
reports:render | POST /cases/{id}/reports, PATCH /reports/{id}, POST /reports/{id}/render |
watches:write | Create/update continuous monitors, review candidates and configure monitor webhooks under /followups |
Creates of cases, notes, report definitions and monitors require a stable
Idempotency-Key (16–120 characters). Reuse it after a timeout: the same
identity/client/path/body returns the original result, including after key
rotation. A changed body or path with that key returns a conflict. Authorizations
are checked again before replay; revoked keys never recover cached private data.
Concurrent creation is atomic with its audit. Admission permits at most 2,000
new replay records per organization per day; existing replays do not consume this
limit. Never reuse an operation key for a deliberately new creation.
Edits and report renders require the exact current If-Match: "<version>".
A conflict requires reading and reconciling the current resource, not guessing
its next version. Rendering the same revision returns its existing version/job;
failed rendering can be retried explicitly. Renders consume no search credits.
Evidence capture accepts either job_id + record_index or an imported
JSON object, plus visibility and optional annotation. Imported data always
remains user_supplied; it is never relabelled as a Profundis observation.
For example, after a search job succeeds in a client folder with cases, create a case in that same folder and capture one returned row. The shared General research space does not own cases:
POST /api/v2/service/organizations/{org}/clients/{client}/cases/{case_id}/evidence
Authorization: Bearer <service-key>
Content-Type: application/json
{"job_id":"<completed-job-id>","record_index":0,"visibility":"internal","annotation":"Review this observation"}
This requires evidence:write and access to the source job; a search-only key
cannot perform it. Use cases:share as well before creating client-visible
cases, comments or evidence. The OpenAPI contract
describes the request bodies for case edits, annotations, report drafts and
monitor definitions.
Capture deduplicates the same source/content. A machine may capture only its own
job results, with the source job's scope still present on the current key.
created_by is null for machine-created resources; service_identity_id and
service_key_id give the actual actor. Machine attribution is included in the
signed evidence manifest.
Continuous monitors use /followups, with name, definition, state,
delivery and notify_kinds. Use delivery: "inbox" for service-key writes:
the key is not an email recipient. See the monitoring guide
for a complete create example and version checks. Monitors belong to the
organization and remain active after key rotation or revocation; explicitly
pause or delete the monitor to stop it. Revocation still stops that key's API
access and pending jobs.
The former scheduled-monitor write routes under /watches return
410 monitors_replaced after cutover. Existing definitions are retained but
not automatically migrated. Create new continuous monitors to resume tracking.
No machine route publishes reports, creates guest links, manages members or
modifies billing. Monitor webhook configuration requires watches:write, current
client access and destination capacity. Prepare report drafts and review them in
the UI. organization_write in MCP exposes case/note/evidence/report preparation
only, with explicit client, action, version and replay key. Its adapter cannot
reach arbitrary URLs or execute paid monitor runs. MCP lists it only for a key that
holds cases:write, evidence:write or reports:render; otherwise the MCP
connection stays read-only and you call the endpoints above directly.
Example case creation:
POST /api/v2/service/organizations/{org}/clients/{client}/cases
Authorization: Bearer <service-key>
Idempotency-Key: customer-recon-case-2026-001
Content-Type: application/json
{"title":"External reconnaissance","visibility":"internal"}
Example note (use a different operation key):
{"body":"Review the observed service before publishing.","visibility":"internal"}
Observed subdomain inventory
POST /tools/subdomains with tools:execute and a stable Idempotency-Key:
{"domain":"example.com","max_results":1000}
max_results defaults to 1,000 and accepts 1–10,000. The browser uses 1,000 and
asks for confirmation before reserving up to 6 credits. Price: 1 credit per 100
names for the first 300, then 1 per 300, rounded up; minimum 1 for a completed
inventory. 10,000 names reserve 36 credits. Only the delivered unique names are
charged; unused reservation is released. An unsuccessful source yields no
inventory and no charge. This uses the organization's/client's envelopes, never
the personal subdomain stream, quota or credit estimate.
The result includes results: [{subdomain}], completed, partial, capped,
reason, max_results, source_records_scanned, coverage: observed_dns_names
and the billing context. Names are normalized, deduplicated and sorted. The
collection stops after the requested name limit or 100,000 DNS observations;
partial identifies a limited inventory. completed describes job completion,
not exhaustive domain coverage. Stored evidence retains this coverage metadata.
The collection reads observed DNS; it does not actively scan a customer domain.
MCP organization_asset_tool accepts subdomains with the same input and an
explicit max_credits sufficient for its requested maximum.
Favicon similarity in a client space
tools:execute also covers POST …/tools/favicon-search with { "hash": "1234567", "max_credits": 3 }. Read the current base price from GET …/favicons/pricing before authorizing a request. max_credits is an optional server-enforced ceiling; MCP always supplies it. The result identifies its source_job_id. Hidden bands contain no hash or host count.
GET …/favicons/{source_job_id}/bands/{band}/quote returns the next band's cost and count. To reveal it, submit POST …/tools/favicon-widen with source_job_id, integer band and the authorized max_credits, plus a stable Idempotency-Key. The private source expires after seven days. A band already paid for on this source is free to retrieve again; concurrent successful jobs settle the charge once. The result's billing_context.credits_charged is authoritative.
Machine credentials may only widen their own identity's sources within the selected client and current tools:execute scope. Price changes or exhausted budgets never fall back to personal credits. A new matching search creates a separate source and may incur a new charge.
In the browser, viewed-icon markers and similarity assessments are private to the named user and client space, retained for 90 days, and capped at 5,000 entries per user/client. These assessments do not train the shared matcher. They are not machine API endpoints. Capturing a revealed match as evidence is free; locked placeholders cannot become evidence.
Usage and budget thresholds
Owners and administrators open the organization-wide report in My account → Organization → Credit usage: all clients by default, with totals, a breakdown by member, operation, client, integration or service key, the operation history and a CSV export. In a client space, Credit usage shows that client only. Analysts can open it in their assigned clients, and external users see only their own operations. The credit bar's Credit usage link opens the organization-wide report for owners and administrators; other members get a link to the current client's report while they work in a client. This accounting view does not accept service credentials and does not contain search queries, emails, API secrets or idempotency keys.
Session endpoints under /api/v2/organizations/{orgId}:
GET /usage/report: filter byclient_id,service_identity_id,service_key_id,operation, RFC 3339from/ exclusiveuntil,limit(1–200) andcursor. Defaults to 30 days; maximum interval 366 days. Returns matching totals, grouped client/identity/key/operation usage, and a stable row-order cursor. Each grouped table shows up to 500 entries and signals truncation separately from complete totals.GET /usage/export: the same filters, without a cursor; UTF-8 CSV, up to 10,000 operations. Larger exports are refused: narrow the interval. No research credits are charged.GET /usage/alerts?client_id=…: threshold history for the current accounting cycle, restricted to the caller's visibility. External users only see their shared external envelope's thresholds.
Actual settled consumption triggers 80% and 100% once per cycle and scope (organization, client, external envelope). Reservations alone do not trigger an alert. Adjusting an envelope does not resend thresholds already reached in that cycle. Client/external events can be delivered through a configured budget.threshold_reached webhook; payloads carry identifiers and a state such as client_80, never the accounting breakdown. Organization-wide thresholds appear in the manager's UI. The admin console displays the latest threshold metadata without research content. These are in-app/webhook notifications; they do not enable email delivery by default.
Workspace Pivot
GET …/clients/{client}/pivot/pricing returns search_cost and expand_cost. POST …/clients/{client}/tools/pivot accepts the Pivot request (seed, seed_kind, signals, depth:1, optional expand_node, exclude_nodes, keep_nodes, include_auto_muted, seed_favicon_hash, pivot_id) and optional max_credits. Service scope: tools:execute. At most eight signals and 50 excluded or restored node IDs; collection deadline 30 seconds; snapshot capped at 499 nodes/1,000 edges. Unindexed seeds cost zero. Failed collection releases credits; usable partial results retain the quoted fixed price and expose partial, signals_completed, signals_failed. No personal billing/entitlement or anonymous fallback is used.
Results carry source_job_id. A named browser user can import 1–10 completed steps through POST …/cases/{case}/pivot, body { "job_ids": ["UUID"], "visibility": "internal" } and If-Match containing the case version. The server reads authorized source jobs, signs inferred-connection evidence and merges the case atomically. External users can import only their own jobs into a shared case with visibility: "client". Oversized sources/merged cases or stale versions reject the entire import. No additional search charge. MCP organization_asset_tool supports Pivot and enforces its authorized ceiling using the workspace price endpoint.
Automatic Pivot and saved graphs
Use tools:execute and a stable idempotency key:
POST /api/v2/service/organizations/{org}/clients/{client}/tools/pivot-explore
Content-Type: application/json
Idempotency-Key: example-pivot-explore-001
{"seed":"example.com","mode":"guided","budget":5,"max_steps":2,"max_credits":5}
Guided exploration includes the initial pivot and up to two expansions for
5 credits. For extended exploration, omit mode and choose budget (at most
100) and max_steps (at most 30, default 8). The server reserves the budget,
settles actual consumption once, and releases the unused amount. Sources that
all fail, or an unindexed starting seed, do not consume credits.
Poll /jobs/{id} and read /jobs/{id}/result after success. This is a background
job, with a collection deadline of six minutes; results are delivered together
at completion. explore.steps, spent, refunded and stop_reason explain the
work done. Partial source failures are reported, not treated as absent assets.
The result is limited to 1,000 nodes, 10,000 edges and 4 MiB. Use the normal
POST /jobs/{id}/cancel route to stop unfinished work. After a connection loss,
re-use the idempotency key to find the original job before launching another.
Completed indexed pivots return pivot_save with a saved id or a storage-failure
status. The result remains in its job even if the saved-library limit is reached.
Use the same organization/client prefix for these free operations:
| Method and path | Purpose |
|---|---|
GET /pivot/saves | List this integration's saved pivots; optional seed filter |
GET /pivot/saves/{id} | Open the stored graph without running a search |
PATCH /pivot/saves/{id} | Rename (title), pin (pinned) or update view |
POST /pivot/saves/{id}/copy | Create a private copy |
POST /pivot/saves/{id}/undo | Restore one of three snapshots; no credit refund |
DELETE /pivot/saves/{id} | Delete the save |
GET /pivot/saves/{id}/stix?tlp=amber | Export the saved graph as STIX |
POST /pivot/saves/{id}/evidence?tlp=amber | Export an evidence package |
To continue automatic exploration, submit the same starting seed and
pivot_id, with an explicitly authorized budget and a new idempotency key.
For a manual extension, use tools/pivot with pivot_id and expand_node.
Only one job may update a given save at a time. Opening its URL never authorizes
another paid exploration.
A key can access saved pivots created by its own integration, including its other keys, within its current context grants. Browser users can share a save with staff who have access to that research space. That sharing does not grant access to external clients or service integrations. There is no public or email-based Teams Pivot sharing.
The library holds at most 150 saves per research space, 1,000 per organization and 64 MiB of graph/view/undo storage per organization. Saved graphs remain until deleted or removed by the organization's retention/erasure process. Evidence capture from an originating job still depends on that job's retention.
Workspace tracking analysis
POST …/clients/{client}/tools/analytics (tools:execute) accepts { "raw_query": "*.example.com", "level": "2", "only_subs": false, "only_unrelated": false, "max_credits": 1 }. The selector may also be an exact tracking ID; domain-scope filters require a hostname. Depth is a string from "1" to "3". Fixed price: 1 credit. Collection is limited to 30 seconds, 25 source queries and 500 connections; partial, source_queries, failed_queries, coverage, connection_limit and query_limit explain coverage. Source errors with no usable connections release the reservation; valid empty results use the fixed price. Results and signed evidence stay within the client workspace. MCP organization_asset_tool supports analytics with the authorized ceiling.
Organization pipelines and related-name lookups
POST …/clients/{client}/tools/pipeline (tools:execute) accepts the existing steps format, with 1–5 sequential steps, limit 1–50 and optional max_credits. Reservation: one credit per step; only successfully completed sources are charged. Domains selected for enrichment are bounded to 50; expanded queries to 20,000 bytes and 200 filters. Results expose partial, failed_steps and coverage: bounded_live_index_pages. Missing backing-store records, capped aggregations and truncated extraction must not be interpreted as negative evidence.
next_cursor is an authorized completed job ID. Resubmit the same normalized steps and limit with cursor; the server recovers the page and rechecks the job owner, client and service identity. Modified queries and foreign continuations are rejected. Pagination reads the live index; it is not a frozen cross-request snapshot. A continuation or enrichment is a new paid job. Organization and lookalike UI searches, full exports and evidence capture keep this context. MCP organization_asset_tool accepts pipeline and checks the step ceiling before sending.
Workspace portfolio comparison
POST …/clients/{client}/tools/portfolio (tools:execute) accepts { "domains": ["example.com", "example.org"], "max_credits": 2 }. One to ten normalized, unique domains; URLs, IP addresses and query syntax are rejected. The maximum reservation is one credit per domain; a domain with all sources unavailable costs zero. Collection is limited to 45 seconds and three source requests per domain: 500 subdomain host observations, 50 root/WWW observations and one WHOIS record.
The existing comparison schema is preserved, with partial, failed_sources, source_queries, source_limit and analysis_scope. Measurements describe returned observations, not live verification or a complete inventory. Partial analyses withhold automatic findings. Capture a domains_details entry as evidence with this coverage. MCP organization_asset_tool supports portfolio; both synchronous and progress UI entry points use the organization job transport.
Personal investigation copies and conflict recovery
Named browser users may POST …/cases/{case}/import with If-Match: "revision", { "source_name": "Personal investigation", "visibility": "internal", "document": { "summary": "…", "nodes": [], "edges": [] } }. The UI previews a normalized personal investigation JSON export before submission. One to 500 nodes, at most 1,000 edges and a final 256 KiB case document; dangling relations, oversized fields and incoming evidence references are rejected. Node notes are limited to 4,096 characters; positions must be finite and within ±1,000,000. External users require a shared case and visibility: "client".
The copy receives signed, chunked user-supplied evidence; it never becomes a trusted search capture. A stale version or oversized merged case rolls back both evidence and document. Repeating the same import deduplicates entities. Personal originals and their share URLs are unchanged.
POST …/cases/{case}/copy, { "title": "Recovered draft", "document": { … } }, creates a new case from an explicitly submitted draft after checking access to the source. Staff copies start internal; external copies remain client-visible. Draft evidence references are replaced with user-supplied provenance. This supports recovery after an edit conflict without overwriting the winning revision. Neither endpoint charges search credits or exposes a service-key route.
Active key capacity
The server catalogue allows 10 active service keys per organization for trial, Flex and existing Teams/Teams Plus subscriptions. All ten may belong to one integration. Creating another identity does not create another allowance. The independent technical limits remain 20 identities and 20 key issuances per hour per organization. For more than ten keys, contact us; increasing Flex's credits or monitor capacity does not increase its key allowance. Rotation may replace an existing slot with one bounded overlap (up to 24 hours). That overlap counts against other issuances until it expires; repeatedly rotating the same predecessor cannot create extra slots. Revoking a key remains possible when the subscription is read-only or expired.
Verify a job webhook with an integration credential
POST .../webhook-events/verify accepts a stored organization service credential
and a JSON object containing endpoint_id, event_id (the
X-Profundis-Event-ID header), signature (the complete
X-Profundis-Signature header), and body (the exact incoming UTF-8 body as a
string). Body limit: 8 KiB; enclosing JSON limit: 16 KiB. No URL is accepted or
fetched, and verification creates no job or credit operation.
This endpoint currently handles job.completed and endpoint.test. It checks
the active endpoint in the explicit client, the HMAC against its current or
still-valid previous secret, a five-minute timestamp tolerance and the original
stored event. It returns minimal event metadata, never the signing secret or job
payload. A key must have a supported job scope (search:read, tools:execute,
exports:create, reports:render or watches:write); the actual job kind's scope
is checked again. Authentic events belonging to another actor return
ignored: true without resource or state data. Fetch an authorized completed
job through its normal /jobs/{id}/result endpoint.
Verification permits legitimate delivery retries; the consumer remains responsible for event deduplication. The n8n receiver provides an importable example with bounded persistent deduplication and empty webhook responses.