Integration configuration
The integration-configuration endpoints let an org admin list which integrations are activated, set or rotate their credentials, and deactivate them — from a script, automation, or AI agent. Common use cases:
- IT provisioning script activates Snowflake on a fresh HG Insights org.
(Only catalog entries marked enabled can be configured; a disabled one is
refused with
integration_disabled, and the list response does not say which are which.) - Quarterly key rotation updates the HG Insights v2 key when the partner rotates their secret.
- Decommissioning removes a credential when an integration is no longer needed.
All operations require an admin-scoped API key.
User-scoped keys receive 403 forbidden_admin_scope on every endpoint
and tool listed here.
Activation model
An integration is active when the per-tenant
configured_integrations table has a row for its key. There is no
separate is_active column. This means:
- Setting credentials activates the integration.
- Rotating credentials (PUT on a key that already has a row) is an upsert — the integration stays active across the rotation, no intermediate down state.
- Deleting credentials deactivates the integration immediately.
The audit log tells activation and rotation apart: every
set_integration_credentials row records metadata.wasUpdate: false
on first set and true on subsequent rotations.
Surfaces
The same three operations are exposed three ways:
| Surface | Path / tool name | Auth |
|---|---|---|
| REST (admin facade) | GET /api/admin/integrations, PUT /api/admin/integrations/{key}/credentials, DELETE /api/admin/integrations/{key}/credentials | Authorization: Bearer <admin_key> or x-api-key |
| MCP tools | admin_list_integrations, admin_set_integration_credentials, admin_remove_integration_credentials | Admin-scoped API key over /api/mcp (Bearer) or /api/ai/{key}/mcp |
| Power Automate REST facade | POST /api/powerautomate/admin_list_integrations (etc.) | Authorization: Bearer <admin_key> |
Underlying business logic is the same across all three surfaces — see
webapp/src/server/api/admin/integration-management.ts.
Security: credentials never leave configured_integrations
The credential value is taken in on PUT, persisted to the
configured_integrations.value column, and never echoed back in any
API response. It also never appears in the MCP metering table — the
admin_set_integration_credentials tool declares value as a sensitive
parameter, and the metering layer redacts it to the literal string
'<redacted>' before persisting telemetry.
Concretely, if you call admin_set_integration_credentials with
value: "phx-secret-key-abc123", you can grep
webapp_tool_metering.metadata and webapp_org_admin_audit_log.metadata
all you like — the plaintext string is never there.
Org isolation
The organization is always derived from the API key
(webapp_api_keys_registry.organization_slug). It is never accepted
from the request body, query, or path. An admin key for org A cannot
read or modify org B's integrations.
Auditing
Every operation writes a row to webapp_org_admin_audit_log:
list_integrations—target_type=null, target_id=null, metadata={count, configuredCount}. The list response reveals which integrations the org has activated, which is itself sensitive — that's why reads are audited.set_integration_credentials—target_type='integration', target_id=<key>, metadata={wasUpdate: boolean}.wasUpdate=trueis a rotation;falseis an activation.remove_integration_credentials—target_type='integration', target_id=<key>, metadata={wasNoop: boolean}.wasNoop=falsemeans a row was actually deleted;truemeans the DELETE was idempotent (no-op).
Polling note: because
list_integrationsis audited, polling the list endpoint at high frequency will fill the audit log. If your automation needs frequent reads, either cache the response client-side or add a debouncing layer. We do not throttle in code — the choice is yours.
GET /api/admin/integrations
List the integration catalog joined with this org's configuration state. Returns metadata only — credential values are never included.
Request
GET /api/admin/integrations
Authorization: Bearer phx_<your-admin-key>
Response (200)
{
"integrations": [
{
"key": "hginsights_v2",
"name": "HG Insights v2",
"description": "Unified API for technographic, firmographic, and intent data.",
"isConfigured": true,
"hasCredentials": true,
"configuredAt": "2026-04-15T10:30:00.000Z",
"updatedAt": "2026-04-29T14:00:00.000Z",
"configuredByEmail": "alice@acme.com"
},
{
"key": "salesforce",
"name": "Salesforce",
"description": "CRM integration",
"isConfigured": false,
"hasCredentials": false,
"configuredAt": null,
"updatedAt": null,
"configuredByEmail": null
}
]
}
hasCredentials aliases isConfigured for clarity in API responses
(presence-of-credentials = active per the model above).
configuredByEmail is null when the integration is not configured or
when the user who originally configured it no longer exists.
Errors
403 forbidden_admin_scope— caller's API key is not admin-scoped.
PUT /api/admin/integrations/{key}/credentials
Set or rotate the credential for an integration. Upserts the
configured_integrations row. Activation if no row existed; rotation
if it did.
Request
PUT /api/admin/integrations/hginsights_v2/credentials
Authorization: Bearer phx_<your-admin-key>
Content-Type: application/json
{
"value": "your-hg-v2-api-key"
}
Response (200)
{
"key": "hginsights_v2",
"isConfigured": true,
"hasCredentials": true,
"updatedAt": "2026-04-29T14:00:00.000Z"
}
The value is not echoed back.
Errors
400 invalid_request— body is not valid JSON or missing/oversizevalue.400 integration_disabled— the integration is in the catalog but disabled at the platform level.400 invalid_credentials— a registered validator rejected the value (e.g., upstream API returned 401 with the new key). The existing row is left intact.403 forbidden_admin_scope— caller's API key is not admin-scoped.404 integration_not_found—{key}does not exist in the catalog.500 tool_setup_failed— credential saved, but a downstream tool-setup step (e.g., TrustRadius aggregator wiring) failed. Retry the same call.
Example: rotate the HG Insights v2 key
curl -X PUT https://phoenix.hginsights.com/api/admin/integrations/hginsights_v2/credentials \
-H "Authorization: Bearer phx_admin_xxx" \
-H "Content-Type: application/json" \
-d '{"value": "your-new-hg-v2-key"}'
{
"key": "hginsights_v2",
"isConfigured": true,
"hasCredentials": true,
"updatedAt": "2026-04-29T14:00:00.000Z"
}
The agent service reads the v2 key per request through the HG MCP Server client — there is no caching beyond the request scope, so the next agent run uses the new key.
DELETE /api/admin/integrations/{key}/credentials
Deactivate an integration by removing its credential. Idempotent —
returns 200 whether or not a row existed. This avoids hostile retries
on automation jobs that have already succeeded.
Request
DELETE /api/admin/integrations/zoominfo/credentials
Authorization: Bearer phx_<your-admin-key>
Response (200)
{
"key": "zoominfo",
"isConfigured": false
}
Errors
403 forbidden_admin_scope— caller's API key is not admin-scoped.404 integration_not_found—{key}does not exist in the catalog at all (this is not the same as "no credential exists" — that case returns 200).
Both calls are audited; the second returns the same payload but writes
metadata.wasNoop: true so the audit trail stays truthful about caller
intent.
Deliver reports to your Amazon S3 bucket
An org admin can have every agent run report copied to an S3 bucket in the org's own AWS account. Phoenix keeps its own copy; the Library, downloads and public links are unchanged. Only reports from runs started after you first configure the integration are copied.
Set it up from Integrations > Amazon S3 in the web app (it has its own page; the admin API above does not configure it):
- Create an IAM role whose trust policy lets Phoenix assume it, with the
external ID shown on the page (
phoenix-<your organization id>). Ask your HG Insights contact for the Phoenix principal to trust. - Grant the role
s3:PutObjectonarn:aws:s3:::<bucket>/<prefix>*.s3:DeleteObjectis optional: without it, the test file stays in the bucket. - Enter the bucket, region, role ARN and an optional prefix (default
phoenix/) and save. Phoenix writes a test file first (and deletes it if the role allows) and refuses to save if the write fails, telling you which step failed.
Objects are written to <prefix>runs/<run_id>/<name>.<ext> with S3-managed
(AES256) encryption. No access keys are shared or stored. If a delivery
fails, the run still succeeds and the integration page shows Last delivery
failed with the reason; it is not retried, and the status clears after the
next successful delivery.
MCP tool inputs
The MCP tools take the same fields as the REST endpoints, except
integration_key is the parameter name (snake_case) instead of a path
segment:
| Tool | Input | Notes |
|---|---|---|
admin_list_integrations | {} | No parameters. |
admin_set_integration_credentials | { "integration_key": string, "value": string } | value is redacted from telemetry. |
admin_remove_integration_credentials | { "integration_key": string } | Idempotent. |
See the MCP tools reference for full per-tool docs.
Validators (future-facing)
Some integrations will eventually register a server-side validator that
the PUT endpoint runs before persisting. When a validator rejects, the
endpoint returns 400 invalid_credentials with the validator's
message — and the existing row stays intact. Today the validator
registry is empty, mirroring the current webapp behavior. New
validators ship in subsequent issues.
Skill / runbook
For day-to-day operational notes (when to rotate, who to notify, common mistakes), see the admin-api-keys runbook.