Skip to content

Integrations

Integrations let you connect a third-party service once at the organization level, then push its credentials into your projects as environment variables. Connecting Stripe, for example, encrypts your secret key at rest and — when you link it to a project — writes STRIPE_SECRET_KEY (and any metadata-derived vars) into that project’s environment and best-effort redeploys it.

Two distinct systems share the underlying integrations table, keyed by a string type:

  • The GitHub App integration (type: "github", reserved) — used for private-repo project creation. It stores only an installation_id; the installation access token is never persisted.
  • Marketplace integrations — third-party services like Sentry, Stripe, and Resend. Their API key is encrypted at rest (AES-256-CBC) and never returned by the API; reads surface only a masked hint plus display-safe metadata.

There is exactly one connection row per (organization, type). Re-connecting a type overwrites the stored credential and metadata.

The catalog is a static, versioned constant served by the API and rendered in the dashboard — there is no per-org catalog database. Brand logos are pulled from the Simple Icons CDN.

Fetch it with:

GET /v1/integrations/catalog

It returns the full list of catalog entries plus the available categories and kinds used for filtering.

Entries are grouped into 16 categories:

AllMonitoringLoggingDatabase
StorageAuthenticationAnalyticsAI
MessagingPaymentsSearchFeature Flags
SecretsBackendMediaCMS
KindMeaning
NativeA first-party / deeply-integrated provider
ExternalA third-party service connected by API key
FieldTypeNotes
idstringStable slug used as the :type in URLs
namestringDisplay name
categorystringOne of the 16 categories above
kindNative | External
statuslive | coming_soonOnly live entries can be connected
connectionTypeapi_keyHow the credential is supplied
provisionsboolean?Deep-provision flag (provisions a real resource)
descriptionstringShort blurb
colorstringBrand color
iconUrlstringSimple Icons CDN URL
websitestringProvider homepage
envVarsstring[]The env var names this integration injects
inputFieldsobject[]?Extra fields the connect form collects (see below)

Each inputField has a key (e.g. api_key or metadata.<name>), label, placeholder, type (text or password), a required flag, and an optional hint.

These integrations are connectable today. Everything else in the catalog is marked coming_soon.

CategoryLive providers
MonitoringSentry, Datadog, New Relic, Rollbar
LoggingLogtail
MessagingResend, Slack
PaymentsStripe
SecretsDoppler
AnalyticsPostHog
Feature FlagsLaunchDarkly, Statsig
AI / SearchFirecrawl

The coming_soon set includes:

  • Deep-provision types (provisions: true) that light up when their provisioners ship: pandastack_postgres, pandastack_ai, upstash, turso, together, replicate, elevenlabs, perplexity.
  • Providers needing live validators: aws-s3, auth0.
  • Others: axiom, svix, liveblocks, knock, novu, plausible, growthbook, meilisearch, fal, supabase, neon, planetscale, mongodb, pinecone, cloudinary, clerk, convex, inngest, hasura, mux, contentful, sanity, datocms.

Connecting a marketplace integration validates the credential before anything is stored, so an invalid key is never persisted.

POST /v1/integrations/:type/connect — requires member+ role.

The connect use case runs these steps in order:

  1. Assert connectable — rejects the reserved github type, unknown types, and any coming_soon entry.
  2. Validate the credential first — the credential is checked against the provider via the injected validator. If validation fails, a user-facing message is thrown and nothing is stored.
  3. Encrypt the api_key (AES-256-CBC).
  4. Upsert the single (org, type) row with the encrypted key plus any metadata.

Re-connecting overwrites both the credential and the metadata.

Terminal window
# Connect Stripe
curl -X POST https://api.pandastack.io/v1/integrations/stripe/connect \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "sk_live_...",
"metadata": {
"publishable_key": "pk_live_...",
"webhook_secret": "whsec_..."
}
}'

Connection status and installed integrations

Section titled “Connection status and installed integrations”

Any org member can read connection status. Keys are always masked; secret metadata is stripped from reads.

GET /v1/integrations/:type/status — one integration’s status.

Returns either:

{
"success": true,
"data": {
"type": "resend",
"connected": true,
"maskedKey": "re_ab…cd12",
"metadata": { "domain": "acme.com" },
"connectedAt": "2026-07-01T12:00:00.000Z"
}
}

or, when not connected:

{ "success": true, "data": { "connected": false } }

GET /v1/integrations/statuses — a map of type → status for every connected integration in the org. The reserved github row is excluded.

The maskedKey never contains the raw key. Certain metadata keys are treated as secrets and are never echoed in a status read:

Secret metadata key (masked/stripped)
webhook_secret
client_secret
secret_access_key
personal_api_key

All other metadata is display-safe and returned as-is: site, org, host, region, bucket, app_name, domain, project.

Env injection is how a connected service actually reaches your running app. Linking a connected integration to one or more projects computes its env vars from the decrypted credential, injects them into each project’s environment, and best-effort redeploys.

POST /v1/integrations/:type/link-projects — requires member+ role.

The link use case:

  1. Computes env vars from the connection’s decrypted credentials using a static per-integration map.
  2. Injects them into each owned project’s env (replace-then-append, so it is idempotent).
  3. Records a ProjectIntegration link that snapshots the injected variable names.
  4. Best-effort redeploys each project — a redeploy failure never fails the link.

It returns which projects were linked and which redeployed.

Terminal window
# Link the Sentry integration to two projects
curl -X POST https://api.pandastack.io/v1/integrations/sentry/link-projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "projectIds": [123, 456] }'

The mapping from a connected integration to project env vars is static. Some examples:

IntegrationInjected env vars
stripeSTRIPE_SECRET_KEY (+ STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET from metadata)
sentrySENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN
resendRESEND_API_KEY
posthogPOSTHOG_API_KEY (+ POSTHOG_HOST)
datadogDD_API_KEY (+ DD_SITE)
slackSLACK_WEBHOOK_URL

How a database integration injects DATABASE_URL

Section titled “How a database integration injects DATABASE_URL”

A deep-provision database integration does not carry a static DATABASE_URL in its metadata map. Instead the provisioner creates a real database resource per project and returns the connection string as DATABASE_URL at provision time. This is why the static env map returns an empty list for those types: the value is only known once the resource exists.

If you provision a PandaStack database directly, its connection URL is available on the database itself — see Connecting to a database.

DELETE /v1/integrations/:type/unlink/:projectId — requires member+ role.

Unlinking strips exactly the injected variable names recorded on the link (your own vars are left untouched), deletes the link row, and best-effort redeploys.

List a project’s links with:

GET /v1/integrations/:type/projects

DELETE /v1/integrations/:type/disconnect — requires member+ role.

Disconnecting cleans up first, then deletes:

  1. Strips the injected env vars from every linked project (link cleanup runs first).
  2. Deletes the connection row — the foreign-key cascade drops the link rows.

Cross-tenant or not-connected requests return 404 (tenant-safe).

The GitHub App integration is separate from the marketplace and powers private-repo project creation. It stores only the installation_id.

EndpointMethodRolePurpose
/v1/integrations/github/connectionGETmemberGitHub App connection status
/v1/integrations/github/authorizePOSTmemberSave installation_id after App install
/v1/integrations/github/repositoriesGETmemberList repos via the installation token
/v1/integrations/github/branches?repo=GETmemberList branches for a repo
/v1/integrations/github/:installationIdDELETEmemberRemove the GitHub App installation
EndpointMethodRolePurpose
/v1/integrations/catalogGETmemberFull marketplace catalog + categories/kinds
/v1/integrations/statusesGETmemberMap type → status for all connected integrations
/v1/integrations/:type/statusGETmemberOne integration’s status (masked)
/v1/integrations/:type/connectPOSTmember+Connect / reconnect (validate + encrypt + upsert)
/v1/integrations/:type/disconnectDELETEmember+Disconnect (strip injected env, delete row)
/v1/integrations/:type/projectsGETmemberProjects linked to this integration
/v1/integrations/:type/link-projectsPOSTmember+Link to projects (inject env + redeploy)
/v1/integrations/:type/unlink/:projectIdDELETEmember+Unlink one project (strip env + redeploy)

All routes use the standard API envelope and require a Bearer token.