Appearance
Integrations
Connect external tools to your audit workflow. The Audit API tracks one row per provider per project, with the providers fixed to: ga4, gsc, sentry, github, gitlab, jira, linear, cloudflare.
Base URL: https://app.krafter.dev/api/v1
List Integrations
Retrieve the integration record for every supported provider. The response always contains one entry per provider — providers that have never been connected return a placeholder row with status: "disconnected" and id: null.
GET /orgs/:org_id/projects/:project_id/audit/integrationsRequired scope: audit:read
Example Request
bash
curl https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations \
-H "Authorization: Bearer kr_live_abc123def456"Example Response
json
{
"data": [
{
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"provider": "github",
"status": "connected",
"last_sync_at": "2025-06-10T14:00:00Z",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-10T14:00:00Z"
},
{
"id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"provider": "jira",
"status": "connected",
"last_sync_at": "2025-06-10T13:45:00Z",
"created_at": "2025-06-02T11:00:00Z",
"updated_at": "2025-06-10T13:45:00Z"
},
{
"id": null,
"provider": "sentry",
"status": "disconnected",
"credentials": {},
"last_sync_at": null,
"inserted_at": null,
"updated_at": null
}
],
"meta": {
"request_id": "U-cmqcgdZ6jluPPPSiy"
},
"error": null
}status is one of connected, disconnected, syncing, error. Connected and previously-connected rows include created_at and updated_at. Placeholder rows for never-connected providers include the literal field inserted_at (instead of created_at) and an empty credentials: {} field. Real credentials are never echoed in either shape — see Security below.
Response is not paginated
The integrations list is a fixed-size array (one entry per supported provider) and is not wrapped with meta.total / meta.next_cursor.
Connect Integration
Connect (or re-connect) a provider for a project. The request body is provider-agnostic: pass the provider's secrets in the credentials map. The body is upserted on the unique (team, project, provider) key, so calling connect again for the same provider replaces the stored credentials.
POST /orgs/:org_id/projects/:project_id/audit/integrations/:provider/connectRequired scope: audit:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
provider | string | One of: ga4, gsc, sentry, github, gitlab, jira, linear, cloudflare. Any other value returns 422 invalid_provider. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
credentials | object | No | Provider-specific credentials (token, refresh token, etc.). Stored encrypted at rest. |
auth_code | string | No | Short-lived OAuth authorization code. When present, it is merged into credentials under the key auth_code before encryption. |
The credentials map is opaque to the API — the per-provider key names (e.g. token, api_token, repository, base_url) are conventions interpreted by the worker that talks to the provider, not validated by the connect endpoint. The dashboard OAuth/token-paste flow at app.krafter.dev/audit/integrations is the canonical source for which keys each provider expects.
Example Request (GitHub via personal access token)
bash
curl -X POST https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations/github/connect \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"credentials": {
"token": "ghp_xxxxxxxxxxxx",
"repository": "acme/web-app"
}
}'Example Request (Provider via OAuth auth code)
bash
curl -X POST https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations/sentry/connect \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"auth_code": "abc123def456"
}'Example Response
json
{
"data": {
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"provider": "github",
"status": "connected",
"last_sync_at": "2025-06-10T14:30:00Z",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-10T14:30:00Z"
},
"meta": {
"request_id": "V-dnrdheA7kmvQQQTjz"
},
"error": null
}last_sync_at is set to the connect timestamp because the upsert refreshes that column. Credentials are never returned.
Error Response
json
// 422 Unprocessable Entity — unsupported provider, or changeset failure
{
"data": null,
"meta": {
"request_id": "W-eoseifB8lnwRRRUka"
},
"error": {
"code": "invalid_provider"
}
}Sync Integration
Trigger a sync with a connected provider. The integration is moved to status: "syncing" synchronously and a background worker pushes/pulls findings and tasks against the external system. Unlike the dashboard form, the API endpoint takes no body — it always syncs everything pending for that provider.
POST /orgs/:org_id/projects/:project_id/audit/integrations/:provider/syncRequired scope: audit:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
provider | string | One of: ga4, gsc, sentry, github, gitlab, jira, linear, cloudflare. |
Request Body
This endpoint accepts no fields. Any body sent is ignored.
Example Request
bash
curl -X POST https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations/github/sync \
-H "Authorization: Bearer kr_live_abc123def456"Example Response
json
// 202 Accepted
{
"data": {
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"provider": "github",
"status": "syncing",
"last_sync_at": "2025-06-10T14:00:00Z",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-10T14:30:00Z"
},
"meta": {
"request_id": "X-fptfjgC9moxSSSVlb"
},
"error": null
}The response reflects the row mid-sync. Poll List Integrations to see when status flips back to connected (or to error if the worker failed) and last_sync_at advances.
Error Responses
json
// 422 Unprocessable Entity — unsupported provider
{
"data": null,
"meta": {
"request_id": "X-fptfjgC9moxSSSVlb"
},
"error": {
"code": "invalid_provider"
}
}json
// 422 Unprocessable Entity — sync could not be enqueued (transient infra issue)
{
"data": null,
"meta": {
"request_id": "X-fptfjgC9moxSSSVlb"
},
"error": {
"code": "request_failed"
}
}Security
Integration credentials submitted to connect (e.g. GitHub token, Jira API token, OAuth auth_code) are encrypted at rest using AES-256-GCM via the platform's Krafter.Secrets vault before being written to the database. The encrypted payload is stored as opaque ciphertext in the audit_integrations.credentials column and is decrypted only when a worker needs to call out to the provider.
Credential material is never echoed back in any API response: connect, sync, and connected entries returned by list omit the credentials field entirely. Disconnected provider entries returned by list include credentials: {} only as a placeholder — never an actual secret.