External integrations and credentials
Updated September 24, 2026 for the develop branch (Stripe, TypeSafe, Resend sending paths, Turnstile, Treg). Every environment variable is listed in ENVIRONMENT.md. For the September 7 implementation overview, see current state. Provider verification statements below refer to their stated dates and accounts; they are not a live inspection of the reader's configuration.
Checked September 5, 2026. The application now includes an optional Exa search/content adapter in Discovery → Business research. It is disabled in the delivered local configuration and has been tested with a simulated provider, not a live account. The global search box still searches loaded company records. Release 0.3 adds OpenAI structured discovery drafts and Resend invitation sending through encrypted per-account key settings. OpenAI audio transcription is implemented with participant and advisor review. Customer-system execution remains unimplemented. See Hosted quickstart.
Public research recommendation
Start with Exa for public-source search and content retrieval. Exa's current Search API supports results and requested content, so a separate crawler is not automatically necessary. Add Firecrawl when an engagement needs broader site crawling or page-extraction behavior beyond that initial scope. Firecrawl also offers search; these products overlap and should not both be purchased by default.
Sources: Exa Search, Exa Contents, Firecrawl API overview.
Exa is implemented for bounded source collection. Firecrawl remains an optional future adapter. No live provider request or provider charge was incurred by the application during this build.
Pre-meeting research flow
- Open Discovery → Research & contact. Enter the public business name and optional official website. All four research areas start selected. Each public query is shown; interview text and existing evidence are not sent to Exa.
- Company overview can be restricted to the official domain. Communities, competitors and industry feeds use the wider web. Each selected area uses one request for up to five results and page text from Exa's fixed HTTPS search endpoint.
- Inspect the original URL and captured text in Collected sources. Research carries into meeting preparation automatically, without source checkboxes. Import a page separately if it should also appear in the Evidence library, with URL, retrieval date, retained-content hash and provider-run lineage.
- Draft the preparation email to the point of contact. Use its reply to prepare the leadership agenda. Save reviewed meeting notes, review team dossiers, and create personal questions from each person's role and duties. Email sending is a separate action after saving the requests.
- Returned interviews supply proposed task cards. Review these drafts, then obtain exact-version owner/performer confirmations. Preserve the original responses and source versions.
Hosted: save the Exa key in Workspace settings. Local-only fallback: set EXA_API_KEY and ENABLE_EXA_RESEARCH=true in ignored configuration, then restart. Use a provider account spending limit: the application limits request count, not a guaranteed dollar amount. The cap is ten requests per tenant account across its companies in a rolling 24-hour window, including failed/ambiguous attempts.
Each request reserves a durable record before contacting Exa. Replaying its idempotency key does not call the provider again. There are no automatic retries. A process interruption can leave a run with an unknown provider outcome; refresh after five minutes marks the unresolved run accordingly. Check the provider account before deliberately starting another run. This is not a provider-side cancellation or exactly-once billing guarantee. Network timeout is 30 seconds and response size is bounded at 2 MB.
Research history retains up to twenty runs in the UI; the database retains earlier run metadata and snapshots. Automatic research-history deletion, background job cancellation, comprehensive source ACLs and production evaluation remain open. Tenant RLS and advisor-only routes protect the current local scope.
Credential and service decisions
| Capability | Current state | Needed before connection |
|---|---|---|
| Exa research search/content | Implemented; per-account configuration; simulated-provider tests pass | Project API key, account spend limit and live acceptance |
| Treg research gateway (structured Company Research) | Adapter implemented behind configuration; request/response shapes are assumptions isolated in server/research-engine/providers/treg-catalog.ts; fake-transport tests only | Real Treg API documentation, catalog confirmation, operator TREG_API_KEY, prices and live acceptance |
| Firecrawl site extraction/crawl | Optional; not implemented or configured | Project API key if selected, URL scope, crawl/depth/cost bounds, live acceptance |
| Model-assisted discovery drafts | Implemented via OpenAI Responses API; simulated tests | Project key, sharing consent and live quality/usage acceptance |
| Sixteen framework analyses | Implemented via OpenAI Responses API; versioned canvases and dependency sequence; simulated-provider tests | Account key, input sharing scope and live quality/usage acceptance |
| Repo Copilot and fill-loop model steps (Anthropic Claude or OpenAI) | Implemented. A company's approved Anthropic (Claude) connection is used first, then its approved OpenAI connection, else none (server/ai-provider.ts). Claude calls go through the official @anthropic-ai/sdk (Messages API, structured JSON output); fake-fetch tests only, no live Claude call made | Anthropic API key in Workspace Settings or a company connection, company approval, and live quality/usage acceptance |
| Audio transcription | Implemented with OpenAI for the advisor web flow; original audio, editable text and advisor recovery; synthetic tests | Account OpenAI key and live microphone/transcription acceptance |
| Companion mobile transcription and understanding | Server-side xAI adapters pinned to grok-voice-transcribe-2.0 and grok-4.7 with store: false and understanding reasoning effort low. Simulated in tests. XAI_API_KEY stays on the server. A missing key is HTTP 503. A provider deadline is HTTP 504. | Production project dutygraph-v2 must have XAI_API_KEY set. Optional XAI_API_BASE defaults to https://api.x.ai/v1. |
| Email (Resend) | Workspace Resend settings send invitations, outreach, kickoff, receipts, reports and advisor notifications; the platform PILOT_NOTIFY_* sender handles lead email and requester acknowledgments; website confirmations use WEBSITE_CONFIRM_* or fall back to PILOT_NOTIFY_*. Reminders, follow-ups and the notification outbox are built; tests use fake or capture providers | Verified sending domain and sender for DutyGraph and for each firm, PILOT_NOTIFY_* on Vercel, delivery/bounce/complaint webhooks (not built), legal review of the footer |
| Payments (Stripe) | Checkout (one-time and subscription), invoices, SOW path and a verified webhook; Connect is the default merchant model; off unless STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are set; fake transport and signed fixtures in tests | Test-mode keys, webhook endpoint, connected account per firm (recorded by an operator; no onboarding flow yet), owner price confirmation, live-mode authorization |
| TypeSafe Jev | Adapter pinned to jev-1.13.0, platform key, per-firm opt-in (off by default), metered in jev_calls; replayed fixtures in tests; 16 manual live calls with fictional data on September 24 | TYPESAFE_API_KEY on Vercel, DPA/subprocessor listing and privacy wording (legal review), firm opt-in |
| Cloudflare Turnstile | Server verification and widget on the public request form when both keys are set | Site and secret keys, a preview-deployment check of the widget and CSP |
| Expo push (Companion) | Opt-in push provider with push_deliveries records | EXPO_PUSH_ENABLED=true, optional EXPO_ACCESS_TOKEN, device acceptance |
| Neo4j Aura graph | Connected advisor account; production projection and duplicate-free repeat rebuild verified September 6, 2026; encrypted settings and PostgreSQL fallback | Each additional advisor account supplies its own Aura connection; larger-workload testing remains open |
| Meeting ingestion | No connection | Authorized account, meeting selection/visibility policy, webhook/replay and lineage validation |
| Enterprise identity/policy | No connection | Customer-specific identities, effective-access sources, membership and approval assurance |
| Signet/runtime/customer actions | Fail-closed unconfigured | Managed keys, legitimate authority, precise action/resource scope, conformance and reconciliation |
The application needs its own service credentials. A connector available to the development assistant does not grant the deployed app a reusable API key or commercial account. Put selected credentials in the project's ignored configuration or approved secret store, never source control or chat. Use Workspace settings for OpenAI, Anthropic (Claude), Exa, Resend and Neo4j Aura. A company uses a workspace key only once approved: under each key, Settings › Research & AI keys lists the workspace's companies to check (GET /api/v1/providers/{provider}/companies, then the company connection PUT with binding firm), or approve it in the company's Connections. Platform-level services (Stripe, TypeSafe, Treg, Turnstile, xAI, the platform email sender) are environment variables because DutyGraph, not the firm, holds those accounts. AES-256-GCM encryption uses a server-only key; database backups alone cannot decrypt credentials. Configuration status does not claim live verification. Workspace Settings shows each key as *Saved but not tested*, *Connected* or *Test failed*: POST /api/v1/providers/{provider}/test runs the same read-only probe as company connections (OpenAI and Anthropic list models, Resend lists domains), at most once a minute per workspace, and keeps the latest result with the saved key (provider_settings.config.test); saving the key or its settings clears it. A test only shows that the provider accepted the key. Exa is not probed: run company research to test it within its approved research budget.
Structured Company Research providers (Treg, Exa fallback)
The structured research engine (docs/research-engine.md) routes each research capability to a preferred provider, then corroboration and fallback providers. Treg is the planned primary gateway and is operator-level configuration, because DutyGraph pays the gateway: ENABLE_TREG_RESEARCH=true, TREG_BASE_URL (alias TREG_API_BASE, HTTPS), TREG_API_KEY and TREG_TIMEOUT_MS (default 30000). The key stays on the server; no browser code imports the research engine, and a test enforces this. Unconfigured, Treg is skipped and research fields stay unknown. The Treg wire format has not been confirmed against real documentation.
Exa is the web fallback and uses the existing per-account Exa key. Exa search produces field values through OpenAI extraction whose quotes are verified against the page. Without OpenAI, Exa search produces no values, and the Exa Agent backup provider (same Exa key, no OpenAI) fills the fields that search left without evidence, so OpenAI is not required for research. OpenAI adds extraction from search results, drafting and transcription. The Exa Agent has not yet been called live. Provider preference, stop conditions and per-call cost caps come from RESEARCH_ROUTING_JSON. JEV_MODE=heuristic keeps evidence judgments deterministic. The simulated provider (RESEARCH_SIMULATION=true, local only, or sandbox companies) returns fictional, clearly labelled fixtures. No live Treg, Exa or OpenAI research call was made during this build.
Research refresh
Added October 6, 2026 (spec). Some Company Repo files refresh from research: market/signals/* (weekly), market/competitors.md (monthly), the hiring fields of company/profile.md (every 2 weeks), the detected technologies in systems/ (monthly) and the rest of company/profile.md (every 3 months). Each of these files has a refresh: frontmatter block with its cadence, fields, sources, last_checked and next_check. People-sourced files never refresh from the web.
- Refresh now (Repo › Details and the Company Research step page) starts a normal research run limited to the file's fields (
refreshPlanonPOST …/research-runs). It uses the same providers, simulation rule, authorization and budget as every run, at most SNAPSHOT, and costs at most the lowest level's budget. New values arrive as findings to review. A person-confirmed value is kept, and a different value is a flag. - Keep research current is off by default. When a person turns it on, the maintenance cron starts due refreshes for that company, at most 1 per company and 3 per tick, within a monthly cap (default 4 refresh runs at the lowest level's budget). It skips sandbox and sample companies, closed accounts and companies without a research source. It stops at the cap and says so on the step page.
- The listed sources are recorded on the file and the run. The providers do not fetch them directly yet; they run their own queries for each field.
- No live provider was called: tests use the simulated provider.
Payments: Stripe
Built in fix sprint E and hardened by the September 24 security review. Details: payment gate.
- Switch on: set
STRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRET(test mode first). Without both, card and provider invoices are off; SOW and offline methods still work.PAYMENTS_PROVIDER=disabledforces it off. - Merchant model:
PAYMENTS_MERCHANT_MODEL=connect(default, decision D2): charges go to the firm's connected Stripe account with the platform fee from the price book. There is no Connect onboarding flow yet: a deployment operator listed inENGAGEMENT_ADMIN_EMAILSrecords the firm'sacct_…id withPUT /api/v1/billing/settings; advisors get 403STRIPE_ACCOUNT_OPERATOR_ONLY. Each offer is bound to the account at send time. - Webhook:
POST /api/webhooks/stripe(raw body, signature with a 5-minute tolerance, livemode check). Handled events:checkout.session.completed,checkout.session.async_payment_succeeded,checkout.session.async_payment_failed,invoice.paid,invoice.payment_failed,customer.subscription.updated,customer.subscription.deleted,charge.refunded,charge.dispute.created. In Connect mode the endpoint must receive events from connected accounts, because the webhook requiresevent.accountto match the offer's bound account. - Not built: Connect onboarding, a refund button or customer portal, Stripe Tax, SOW PDF, department upgrades on an active engagement.
TypeSafe Jev
Built in fix sprint G. Details: TypeSafe Jev. TYPESAFE_API_KEY is a platform key held only by the server (decision D15). Nothing about a prospect or employee is sent until a firm turns on Use TypeSafe judgment in Workspace Settings; DutyGraph's own website-request triage has a separate platform switch, JEV_LEAD_TRIAGE. Data is minimised (role tokens instead of names, roster header shapes instead of values, a lead's email reduced to a domain class). Code decides and Jev can only make a decision stricter; without a judgment the gates report unchecked. Calls are metered per firm in jev_calls with a daily token cap. TypeSafe is listed as a subprocessor; the privacy/DPA wording is a placeholder for legal review.
Email: Resend
Two kinds of sender exist:
- The firm's sender (Workspace Settings → Resend key and from-address): outreach, invitations, kickoff, receipts, reports, advisor notifications. Outreach also needs the advisor's sender identity (name, firm, postal address) or it is refused with
409 SENDER_IDENTITY_INCOMPLETE. Every advisor-sourced email carries the compliance footer,List-Unsubscribeand one-click unsubscribe (outreach compliance). - DutyGraph's platform sender (
PILOT_NOTIFY_RESEND_KEY,PILOT_NOTIFY_FROM,PILOT_NOTIFY_TO): new-lead email to operators, the requester acknowledgment (always platform-sent, verified addresses only), and the fallback for advisor notifications when a workspace has no Resend settings. Website double opt-in confirmations useWEBSITE_CONFIRM_RESEND_KEY/WEBSITE_CONFIRM_FROM, falling back toPILOT_NOTIFY_*; with no sender configured, double opt-in is inactive and requests count immediately.
Before real sends: verify the sending domain in Resend (SPF/DKIM), decide the platform from-address, and have the footer and privacy copy reviewed. Delivery, bounce and complaint webhooks are not built.
Cloudflare Turnstile
TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY together turn on the challenge on the public request form: the widget renders only when the form-token response names a site key, and the server verifies the token. The site key also adds https://challenges.cloudflare.com to the Express CSP. Without them the form still uses a signed form token, a minimum fill time and per-family rate limits. Check the widget on a preview deployment before release.
Optional Neo4j Aura projection
Settings accepts an Aura neo4j+s://…databases.neo4j.io URI, database username, database name and database password. These are database credentials, not an account sign-in password. Saving enables metadata sync across the advisor account's companies. Test connection performs a live connectivity check; Build company graph requests the current company projection. On September 6, 2026, authorized operator setup connected the advisor account to Aura instance a2d3a8fb. Production maintenance copied Cobalt revision 206: 71 records and 147 relationships. The shared reader validated the snapshot, and a repeat rebuild preserved counts without duplicates. See verification/2026-09-06-guided-release.md for the acceptance details.
Only graph metadata and typed links are copied: record IDs, kinds, titles, states, versions and hashes. Full source bodies, audio and credentials are excluded. PostgreSQL remains authoritative. An unavailable or outdated Aura snapshot falls back to current PostgreSQL records. Removing the connection stops later access but does not remove existing copies from Aura. The verified connection belongs to the configured advisor account; other accounts must configure and test their own connection.
Anthropic (Claude): Repo Copilot and fill loop
Added October 5, 2026 (migration 0042-anthropic-provider). Anthropic is a second AI provider for two features only: the Repo Copilot (copilot.md) and the fill loop's model steps (shape, extract, work plan; fill-loop handoff). Research extraction, discovery drafts, framework analyses, summaries, voice interviews and transcription still use OpenAI only.
- Keys: the same sealed storage as OpenAI. A workspace key lives in
provider_settings(Workspace Settings → *Anthropic (Claude)*); a company uses Claude only through its own approved connection incompany_provider_connections(a company-only key, or an approved binding to the firm key). Hosted deployments never readANTHROPIC_API_KEY. - Which provider:
resolveAiProviderinserver/ai-provider.ts: the company's approved Anthropic connection, else its approved OpenAI connection, else none. When both are connected, DutyGraph uses Claude. Sandbox companies use only an injected provider. - Models:
shared/ai-models.tsanthropicModelIds(claude-sonnet-5-5,claude-opus-5-5,claude-haiku-4-5). A connection may pin one; without one the Copilot uses Claude Sonnet 5.5 at effortmediumand the fill loop Claude Opus 5.5 at its default effort. Haiku 4.5 gets no effort setting. - Call:
server/anthropic.tsbuilds the official SDK client (@anthropic-ai/sdk) from the resolved key only (authToken: null, fixedbaseURL,maxRetries: 0,logLevel: "off", the transport passed as the SDK'sfetch) and callsclient.messages.create({ model, max_tokens, system, messages, output_config: { format: { type: "json_schema", schema }, effort? } }). The schema is the same Zod-derived strict schema the OpenAI path sends, without the keywords Claude's structured outputs do not accept (lengths, ranges, patterns,maxItems); the existing Zod validation still checks the full constraints afterwards. Thinking is left adaptive (neverdisabled, nobudget_tokens); no OpenAI-only parameter such asstoreis sent. - Failures:
stop_reasonrefusal→REFUSED,max_tokensor any unfinished turn →INCOMPLETE; partial output is never used. SDK errors map by class to the existing codes (HTTP_401,HTTP_429,HTTP_5xx,TIMEOUT,NETWORK_ERROR), so the fill loop's transient retries and the Copilot's502 AI_FAILEDwork unchanged. - Key test:
probeProvider("anthropic")lists one page of models through the SDK (client.models.list({ limit: 1 })), read-only and inside the existing once-a-minute test limit. A refused key reads *Test failed*. - Usage rows: the Copilot's
provider_jobsrow and audit event, and the fill jobs' results, recordprovider/aiProviderandmodelas counts-only metadata; prompts, outputs and keys are never stored or logged. - Verification: fake-fetch tests only (
tests/anthropic-provider.test.ts,tests/copilot-api.test.ts,tests/fill-jobs.test.ts,tests/providers-test-route.test.ts). No live Claude call has been made.
OpenAI framework analyses
Strategy uses the account's encrypted OpenAI key and selected supported model. Each of the sixteen frameworks has specific system instructions, an explicit variable-input contract, and a strict output schema. A request includes relevant accepted evidence, bounded public-research excerpts, full current direct-upstream analyses, and brief summaries of other completed analyses. Public research stays labeled unverified. Source text is data, never instructions. The app validates section identities, table columns, citation IDs, confidence labels and required input assessments before saving a complete result.
Runs use the Responses API with store: false. The app retains its own versioned input snapshot, prompt version, model, provider usage and result in provider_jobs. Provider output is an unreviewed analysis, not an authoritative work record. Current framework outputs can inform group reports and the copilot; source citations can reopen the exact saved canvas version.
All required upstream analyses must be current. Source changes and upstream reruns invalidate affected downstream analyses recursively. One click runs one framework. Run remaining sequence can run up to sixteen ready analyses in dependency order. These are user-started actions; no recurring paid schedule is configured. The separate framework cap is 32 attempts per tenant account per 24 hours, including failed or uncertain attempts. Requests reserve durable records before calling the provider, reuse idempotency keys, and do not retry provider failures automatically. Tests and the populated-canvas preview used a simulated provider; no paid framework call was made during verification.
See Live framework canvases for source limits and the full output inventory.
Acceptance of a future research connection
Before marking a connection live, verify one authorized request against the actual provider account, retained URL/date/content lineage, bounded result and cost limits, source review state, retry/cancel behavior, redacted error logs and tenant visibility. Public website material remains untrusted evidence. It must not become application instructions or update confirmed work without review.
Decide whether queries can contain company names or confidential context. A public research search generally does not need private interview text. Site crawling should use an explicit public URL scope and must not expose local/private-network addresses or authentication material. These controls belong in the implementation and tests, not only in an operator note.
No customer permission or execution capability follows from search, scraping, analysis or task confirmation. Those remain separately accepted systems.
Audio transcription and reviewed evidence
Participants can save a recording, create an editable transcript, and add the checked text to their response. If a participant sends only audio, the advisor can open the returned response, create a transcript, correct it, and choose Save reviewed transcript as evidence. This creates a separate accepted evidence record linked to the exact original response and transcription job. It does not rewrite the participant’s submission. Discovery can use that reviewed text; changing or retracting its evidence makes affected drafts stale.
Transcription uses gpt-4o-transcribe, the account’s encrypted OpenAI key, a 105-second timeout, and bounded audio (25 MB) and response text. Completed attempts are reused. Failed or uncertain attempts need an explicit retry. The app limits attempts to three per recording and fifty per account per 24 hours. Audio retention also clears unreviewed provider transcript text. Accepted transcript evidence remains in the work record. No live paid transcription or real invitation email was used during automated verification.