Skip to main content

Scoped service API

Create an integration in Organizations → API integrations. Select its client spaces, permissions and a maximum credit usage per cycle. 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.

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}

Use the API base URL shown by your installation. 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.

ScopeImplemented operations
search:readSearches, batch submission, job states/results, bounded cancellation and retry
tools:executeBounded asset tools
exports:createPrivate export jobs, files and cancellation
cases:readCases, notes, revision history and evidence in assigned client spaces
cases:write, evidence:write, reports:render, watches:writeExplicit machine writes described below
reports:readReport lists, versions and permitted downloads
watches:readWatches, runs, observed changes and monitoring summary

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.

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 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. This response is not a quote or a guarantee that the next operation fits all applicable 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.

Continue an engagement with Claude or Codex​

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.

POST /api/v2/service/organizations/{org}/clients/{client}/search/hosts
Content-Type: application/json
Idempotency-Key: investigation-example-hosts-001

{"raw_query":"host:example.com","page":1,"results_per_page":30}

Engines: hosts, dns, vhosts (certificates), whois, and unified. Responses describe an asynchronous job. Poll GET /jobs/{job_id} until it succeeds, then read GET /jobs/{job_id}/result. A result read does not launch another search. A failed or cancelled job exposes a diagnostic code.

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, watch, 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 Business 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.

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 /batches and GET /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 stable Idempotency-Key for 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​

PathPurpose
/cases, /cases/{id}Case list/detail
/cases/{id}/comments, /cases/{id}/evidenceCase notes and signed evidence
/reports, /reports/{id}/versionsReport definitions and immutable versions
/report-versions/{id}/download/{format}html, pdf, json, or csv artifact
/watches, /watches/{id}/runsWatch configuration and collection history
/changes, /monitoring-summaryObserved changes and counters

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​

CodeResponse
permission_denied / not_foundCheck key, assigned client and scopes. Do not switch payer.
credit_budget_exceededAdjust the authorized organization/client/integration ceiling before resubmitting.
entitlement_requiredThe workspace is read-only; the owner can review its subscription.
idempotency_conflictThe same key was used with a different request. Inspect the original operation.
capacity_exceededReduce the batch/concurrency or wait for existing work.
trial_restricted_fieldA trial cannot use natural-person WHOIS fields. Remove them from the query, sort or field selection.

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.

ToolInputReservation 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 stable Idempotency-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 Business; 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, Business 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.

ScopeMethod and path (relative to the client prefix)
cases:writePOST /cases, PATCH /cases/{id}, POST /cases/{id}/comments
evidence:writePOST /cases/{id}/evidence, PATCH /cases/{id}/evidence/{evidenceId}
reports:renderPOST /cases/{id}/reports, PATCH /reports/{id}, POST /reports/{id}/render
watches:writePOST /watches/preview, POST /watches, PATCH /watches/{id}, POST /watches/{id}/run

Creates of cases, notes, report definitions and watches 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. 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.

Watch input includes name, dataset, query, state (active/paused), scheduled, interval_hours (24/168), max_pages (1–10), and credit_limit per billing cycle. Preview quotes the bounded collection without running a search. Runs require an Idempotency-Key; organization, client, integration and watch budgets apply together. Scheduled runs remain bound to the key that last saved the watch: expiry, revocation, scope removal or client removal stops execution. To transfer automation during rotation, PATCH with the new key and current version. Saving via the UI transfers responsibility to the authenticated person. Pausing stops queued execution too. Existing observations and credit history stay.

No machine route publishes reports, creates guest links, sends messages, manages members or modifies billing. Prepare a draft through automation and review it 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 watch 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 by client_id, service_identity_id, service_key_id, operation, RFC 3339 from / exclusive until, limit (1–200) and cursor. 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, seed_favicon_hash) and optional max_credits. Service scope: tools:execute. At most six signals and 50 excluded nodes; 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.

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.

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 limits live service keys across the whole organization: trial 1, Teams 3, Business 10. Creating another service identity does not create another allowance. The independent technical limits remain 20 identities, three live keys per identity, and 20 key issuances per hour per organization. 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.