Skip to content
Runtime Configuration

Runtime Configuration

Registering a Runtime App

A runtime app is the executable identity of your provider on the Aiffinity platform. It holds your OAuth client credentials, webhook configuration, and runtime endpoint URLs. Each developer account can register up to 10 apps.

Register an app by calling POST /v1/providers/runtime/apps:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Weather",
    "description": "Real-time weather data and forecasts",
    "homepage_url": "https://acmeweather.com",
    "runtime_url": "https://api.acmeweather.com/aiffinity",
    "redirect_uris": ["https://acmeweather.com/callback"],
    "webhook_url": "https://acmeweather.com/webhooks/aiffinity",
    "health_check_url": "https://acmeweather.com/health",
    "logo_url": "https://acmeweather.com/logo-512.png"
  }'

Response:

{
  "app_id": "app_01J8XKWM3N4QR5STVYZ2BCDEFG",
  "client_id": "ci_acmeweather_a1b2c3d4",
  "client_secret": "cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "name": "Acme Weather",
  "status": "active",
  "created_at": "2026-04-04T10:00:00Z"
}

Using the SDK:

import { ProviderPlatformClient } from '@aiffinity/provider-platform-sdk';

const client = new ProviderPlatformClient({
  clientId: process.env.AIFFINITY_CLIENT_ID,
  clientSecret: process.env.AIFFINITY_CLIENT_SECRET,
});

const app = await client.runtime.register({
  name: 'Acme Weather',
  description: 'Real-time weather data and forecasts',
  homepageUrl: 'https://acmeweather.com',
  runtimeUrl: 'https://api.acmeweather.com/aiffinity',
  redirectUris: ['https://acmeweather.com/callback'],
  webhookUrl: 'https://acmeweather.com/webhooks/aiffinity',
  healthCheckUrl: 'https://acmeweather.com/health',
  logoUrl: 'https://acmeweather.com/logo-512.png',
});

console.log(app.appId);        // "app_01J8XKWM3N4QR5STVYZ2BCDEFG"
console.log(app.clientId);     // "ci_acmeweather_a1b2c3d4"
console.log(app.clientSecret); // "cs_live_xxx..."
FieldTypeRequiredDescription
namestringrequiredDisplay name shown in the catalog and console
descriptionstringrequiredShort description of your service (max 280 chars)
runtime_urlstringrequiredBase URL where Aiffinity sends capability execution requests
homepage_urlstringoptionalPublic URL for your service
redirect_urisstring[]optionalAllowed OAuth redirect URIs (up to 5)
webhook_urlstringoptionalURL for receiving platform webhook events
health_check_urlstringoptionalURL Aiffinity pings to verify availability
logo_urlstringoptionalURL to a square logo (min 256x256px, PNG or SVG)

Store your client_secret securely. It is shown only once at creation time and cannot be retrieved later. If lost, rotate the secret via POST /v1/providers/runtime/apps/{app_id}/rotate-secret.

Auth Profile Configuration

The auth profile tells Aiffinity how your app authenticates end users. Three strategies are supported: OAuth 2.1, API key, and credential-based. Configure your profile via PUT /v1/providers/runtime/apps/{app_id}/auth.

OAuth 2.1 (Authorization Code + PKCE)

Best for apps that need delegated access to a user's account on your service. Aiffinity manages the full authorization code flow with PKCE, token refresh, and secure storage.

curl -X PUT https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/auth \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "strategy": "oauth2",
    "oauth2": {
      "authorization_url": "https://acmeweather.com/oauth/authorize",
      "token_url": "https://acmeweather.com/oauth/token",
      "revocation_url": "https://acmeweather.com/oauth/revoke",
      "scopes": ["weather:read", "forecast:read"],
      "pkce_required": true,
      "token_endpoint_auth_method": "client_secret_post"
    }
  }'

Using the SDK:

await client.runtime.setAuthProfile(app.appId, {
  strategy: 'oauth2',
  oauth2: {
    authorizationUrl: 'https://acmeweather.com/oauth/authorize',
    tokenUrl: 'https://acmeweather.com/oauth/token',
    revocationUrl: 'https://acmeweather.com/oauth/revoke',
    scopes: ['weather:read', 'forecast:read'],
    pkceRequired: true,
    tokenEndpointAuthMethod: 'client_secret_post',
  },
});

PKCE recommended: Enable Proof Key for Code Exchange for all OAuth flows. Aiffinity enforces PKCE for public clients and strongly recommends it for confidential clients.

API Key

For services where users provide a personal API token. The key is encrypted at rest and injected into capability requests via the configured header.

curl -X PUT https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/auth \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "strategy": "api_key",
    "api_key": {
      "header_name": "X-Api-Key",
      "instructions": "Find your API key at Settings > API in your Acme Weather dashboard.",
      "validation_url": "https://api.acmeweather.com/v1/me"
    }
  }'

Using the SDK:

await client.runtime.setAuthProfile(app.appId, {
  strategy: 'api_key',
  apiKey: {
    headerName: 'X-Api-Key',
    instructions: 'Find your API key at Settings > API in your Acme Weather dashboard.',
    validationUrl: 'https://api.acmeweather.com/v1/me',
  },
});

Credential-based

For services that require a username/password or compound credential (e.g. subdomain + token). Aiffinity encrypts and stores all fields. Use this when the target service has no OAuth or API key support.

curl -X PUT https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/auth \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "strategy": "credential",
    "credential": {
      "fields": [
        { "name": "subdomain", "label": "Subdomain", "type": "text", "required": true },
        { "name": "api_token", "label": "API Token", "type": "secret", "required": true }
      ],
      "validation_url": "https://{subdomain}.acmeweather.com/api/v1/verify",
      "instructions": "Enter your subdomain and API token from Settings > Integrations."
    }
  }'

Using the SDK:

await client.runtime.setAuthProfile(app.appId, {
  strategy: 'credential',
  credential: {
    fields: [
      { name: 'subdomain', label: 'Subdomain', type: 'text', required: true },
      { name: 'api_token', label: 'API Token', type: 'secret', required: true },
    ],
    validationUrl: 'https://{subdomain}.acmeweather.com/api/v1/verify',
    instructions: 'Enter your subdomain and API token from Settings > Integrations.',
  },
});

Capability Template Definition

Capability templates declare what your app can do. Each template specifies a mode, a schema, and optional surface guidance. Aiffinity supports five modes:

Mode Description Direction
state Read-only snapshot of current data (e.g. weather, portfolio value) Provider → Aiffinity
action Mutating operation (e.g. create task, send message) Aiffinity → Provider
history Paginated historical records (e.g. commit log, order history) Provider → Aiffinity
realtime Streaming data via SSE or WebSocket (e.g. live score, stock ticker) Provider → Aiffinity
webhook Push events from the provider (e.g. new commit, order placed) Provider → Aiffinity

Register a state capability template:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/capabilities \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "current_weather",
    "mode": "state",
    "description": "Current weather conditions for a location",
    "refresh_interval_seconds": 900,
    "schema": {
      "type": "object",
      "properties": {
        "location": { "type": "string" },
        "temperature_c": { "type": "number" },
        "condition": { "type": "string", "enum": ["sunny","cloudy","rainy","snowy","windy","stormy"] },
        "humidity_pct": { "type": "number", "minimum": 0, "maximum": 100 }
      },
      "required": ["location", "temperature_c", "condition"]
    },
    "surface_guidance": {
      "intent": "ambient_context",
      "placement": ["home_widgets", "daily_brief"],
      "preferred_components": ["stat_row", "sparkline"]
    }
  }'

Register an action capability template:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/capabilities \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "create_task",
    "mode": "action",
    "description": "Create a new task in the user'\''s project",
    "schema": {
      "input": {
        "type": "object",
        "properties": {
          "title": { "type": "string", "maxLength": 255 },
          "description": { "type": "string" },
          "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
          "assignee_id": { "type": "string" }
        },
        "required": ["title"]
      },
      "output": {
        "type": "object",
        "properties": {
          "task_id": { "type": "string" },
          "url": { "type": "string", "format": "uri" }
        }
      }
    },
    "confirmation_required": true,
    "risk_level": "action_capable"
  }'

Register a history capability template:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/capabilities \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "recent_transactions",
    "mode": "history",
    "description": "Paginated list of recent financial transactions",
    "max_items_per_page": 50,
    "cursor_field": "transaction_id",
    "schema": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "amount": { "type": "number" },
        "merchant": { "type": "string" },
        "date": { "type": "string", "format": "date" }
      }
    }
  }'

Register a realtime capability template:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/capabilities \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "price_ticker",
    "mode": "realtime",
    "description": "Live stock price updates via SSE",
    "protocol": "sse",
    "heartbeat_interval_seconds": 30,
    "schema": {
      "type": "object",
      "properties": {
        "symbol": { "type": "string" },
        "price": { "type": "number" },
        "change": { "type": "number" },
        "timestamp": { "type": "string", "format": "date-time" }
      }
    }
  }'

Register a webhook capability template:

curl -X POST https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/capabilities \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "calendar_event_changed",
    "mode": "webhook",
    "description": "Receives calendar event create/update/delete pushes",
    "events": ["created", "updated", "deleted"],
    "schema": {
      "type": "object",
      "properties": {
        "event_id": { "type": "string" },
        "title": { "type": "string" },
        "start": { "type": "string", "format": "date-time" },
        "end": { "type": "string", "format": "date-time" }
      }
    },
    "signature_header": "X-Signature-256"
  }'

Using the SDK (state example):

await client.runtime.addCapability(app.appId, {
  name: 'current_weather',
  mode: 'state',
  description: 'Current weather conditions for a location',
  refreshIntervalSeconds: 900,
  schema: {
    type: 'object',
    properties: {
      location: { type: 'string' },
      temperature_c: { type: 'number' },
      condition: { type: 'string', enum: ['sunny', 'cloudy', 'rainy', 'snowy', 'windy', 'stormy'] },
      humidity_pct: { type: 'number', minimum: 0, maximum: 100 },
    },
    required: ['location', 'temperature_c', 'condition'],
  },
  surfaceGuidance: {
    intent: 'ambient_context',
    placement: ['home_widgets', 'daily_brief'],
    preferredComponents: ['stat_row', 'sparkline'],
  },
});

Webhook Profile Setup

Webhook profiles configure how Aiffinity delivers platform events to your service. Each profile defines the target URL, event filter, signing secret, and retry policy. All deliveries are signed with HMAC-SHA256 for verification.

curl -X PUT https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://acmeweather.com/webhooks/aiffinity",
    "events": [
      "package.submitted",
      "package.published",
      "package.suspended",
      "install.created",
      "install.removed",
      "capability.invoked",
      "capability.failed"
    ],
    "signature": {
      "algorithm": "hmac-sha256",
      "header": "X-Aiffinity-Signature",
      "secret": "whsec_your-signing-secret"
    },
    "timeout_ms": 5000,
    "retry_policy": {
      "max_retries": 5,
      "backoff": "exponential",
      "initial_delay_ms": 1000,
      "max_delay_ms": 300000,
      "max_elapsed_hours": 24
    }
  }'

Using the SDK:

await client.runtime.setWebhookProfile(app.appId, {
  url: 'https://acmeweather.com/webhooks/aiffinity',
  events: [
    'package.submitted',
    'package.published',
    'install.created',
    'install.removed',
    'capability.invoked',
    'capability.failed',
  ],
  signature: {
    algorithm: 'hmac-sha256',
    header: 'X-Aiffinity-Signature',
    secret: process.env.AIFFINITY_WEBHOOK_SECRET,
  },
  timeoutMs: 5000,
  retryPolicy: {
    maxRetries: 5,
    backoff: 'exponential',
    initialDelayMs: 1000,
    maxDelayMs: 300000,
    maxElapsedHours: 24,
  },
});
FieldTypeRequiredDescription
urlstringrequiredHTTPS endpoint that receives webhook POST requests
eventsstring[]requiredEvent types to subscribe to (use ["*"] for all)
signature.algorithmstringrequiredSigning algorithm (hmac-sha256)
signature.headerstringoptionalHeader name for signature (default: X-Aiffinity-Signature)
signature.secretstringoptionalHMAC signing secret (auto-generated if omitted)
timeout_msnumberoptionalResponse timeout in ms (default: 5000, max: 30000)
retry_policyobjectoptionalRetry configuration for failed deliveries

The signature.secret is used to compute an HMAC-SHA256 digest of the raw request body. Aiffinity sends the hex-encoded digest in the configured header. Verify it server-side before processing any event. See the Webhooks page for detailed verification code and payload schemas.

Credential Secret Management

Credential secrets are encrypted key-value pairs stored alongside your app. Use them for upstream API keys, signing secrets, database connection strings, or any sensitive configuration your capability handlers need at runtime. All secrets are encrypted at rest with AES-256-GCM.

# Set a secret
curl -X PUT https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/secrets/WEATHER_API_KEY \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "value": "wk_live_abc123def456" }'

# List secrets (values are redacted)
curl -X GET https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/secrets \
  -H "Authorization: Bearer $TOKEN"

# Delete a secret
curl -X DELETE https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID/secrets/WEATHER_API_KEY \
  -H "Authorization: Bearer $TOKEN"

Response from listing secrets:

{
  "secrets": [
    {
      "key": "WEATHER_API_KEY",
      "created_at": "2026-04-04T10:05:00Z",
      "updated_at": "2026-04-04T10:05:00Z"
    },
    {
      "key": "WEBHOOK_SIGNING_KEY",
      "created_at": "2026-04-04T10:06:00Z",
      "updated_at": "2026-04-04T10:06:00Z"
    }
  ]
}

Using the SDK:

// Set a secret
await client.runtime.setSecret(app.appId, 'WEATHER_API_KEY', 'wk_live_abc123def456');

// List all secret keys (values are never returned)
const secrets = await client.runtime.listSecrets(app.appId);
console.log(secrets); // [{ key: 'WEATHER_API_KEY', createdAt: '...' }]

// Delete a secret
await client.runtime.deleteSecret(app.appId, 'WEATHER_API_KEY');

Secret limits: Each app can store up to 20 secrets. Individual secret values are limited to 4 KB. Secret keys must match /^[A-Z][A-Z0-9_]{1,63}$/.

Secrets are scoped to your app and injected into capability handler requests via the context.secrets object. They are never exposed in API responses, webhook payloads, or logs.

Health Check Endpoint

Every runtime app must expose a health check endpoint. Aiffinity pings this endpoint every 60 seconds to verify your service is reachable. If three consecutive checks fail, your app's capabilities are temporarily suspended and users see a degraded status badge.

Requirements:

Expected response format:

GET https://acmeweather.com/health

{
  "status": "ok",
  "version": "1.2.3",
  "uptime_seconds": 86400,
  "capabilities": {
    "current_weather": "healthy",
    "create_alert": "healthy"
  }
}

Minimal implementation in Express:

import express from 'express';

const app = express();

app.get('/health', (_req, res) => {
  res.json({
    status: 'ok',
    version: process.env.APP_VERSION || '0.0.0',
    uptime_seconds: Math.floor(process.uptime()),
  });
});

app.listen(3000);

If your health check endpoint returns a non-200 status or times out, Aiffinity sends a capability.failed webhook event with reason health_check_timeout. Once your endpoint recovers, capabilities are automatically restored within 60 seconds.

Update the health check URL at any time:

curl -X PATCH https://api.aiffinity.me/v1/providers/runtime/apps/$APP_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "health_check_url": "https://acmeweather.com/healthz" }'