Skip to main content

Integration YAML Configuration

Overview​

Sulla uses YAML files to define third-party API integrations. Each integration consists of an auth config file (credentials, base URL) and one or more endpoint config files (API operations). These YAML definitions are loaded at startup and used by ConfigApiClient to make authenticated API calls.

Integrations repository: https://github.com/merchantprotocol/integrations

Local path: ~/sulla/integrations/

Directory Structure​

~/sulla/integrations/
youtube/
youtube.v3-auth.yaml ← Auth config (base URL, credentials)
search.v3.yaml ← Endpoint: search
videos.v3.yaml ← Endpoint: list videos
channels.v3.yaml ← Endpoint: list channels
postmark/
postmark.v1-auth.yaml
email-send.v1.yaml
emails-list.v1.yaml
attio/
attio.v2-auth.yaml
records-list.v2.yaml
records-create.v2.yaml
...

File naming conventions:

  • Auth files: {name}.v{version}-auth.yaml
  • Endpoint files: {endpoint-name}.v{version}.yaml

Currently 146 integrations are available in the repository.

Auth Config Format​

The auth file defines the API base URL and authentication method.

API Key Auth​

api:
name: postmark-v1
version: v1
provider: postmark
base_url: https://api.postmarkapp.com

auth:
type: apiKey
client_secret: '${POSTMARK_SERVER_TOKEN}'
header: X-Postmark-Server-Token
token_storage: local
refresh_automatically: false

Bearer Token Auth​

api:
name: attio-v2
version: v2
provider: attio
base_url: https://api.attio.com/v2

auth:
type: bearer
client_secret: '${ATTIO_ACCESS_TOKEN}'
token_storage: local
refresh_automatically: false

OAuth2 Auth​

api:
name: youtube-data-v3
version: v3
provider: google
base_url: https://www.googleapis.com

auth:
type: oauth2
flow: authorization_code
client_id: '${GOOGLE_CLIENT_ID}'
client_secret: '${GOOGLE_CLIENT_SECRET}'
authorization_url: https://accounts.google.com/o/oauth2/v2/auth
token_url: https://oauth2.googleapis.com/token
pkce: true
scopes:
- https://www.googleapis.com/auth/youtube.readonly
refresh_automatically: true
token_expiry_buffer_seconds: 300

API Key Fallback​

Some integrations support both OAuth and API key authentication:

api_key_fallback:
enabled: true
param_name: key
value: '${YOUTUBE_API_KEY}'

Auth Config Reference​

FieldTypeDescription
api.namestringAPI identifier
api.versionstringAPI version
api.providerstringProvider name
api.base_urlstringBase URL for all endpoints
auth.type'oauth2' | 'apiKey' | 'bearer'Authentication method
auth.client_idstringOAuth client ID
auth.client_secretstringOAuth client secret, API key, or bearer token
auth.headerstringHeader name for apiKey type (e.g., X-API-Key)
auth.authorization_urlstringOAuth authorization endpoint
auth.token_urlstringOAuth token endpoint
auth.pkcebooleanEnable PKCE for OAuth
auth.scopesstring[]OAuth scopes
auth.refresh_automaticallybooleanAuto-refresh expired OAuth tokens
auth.token_storagestringToken storage method
api_key_fallback.enabledbooleanEnable API key fallback
api_key_fallback.param_namestringQuery parameter name for API key
api_key_fallback.valuestringAPI key value or ${ENV_VAR}

Environment Variable References​

Use ${ENV_VAR} syntax in YAML to reference environment variables. These are resolved at runtime:

auth:
client_secret: '${MY_API_KEY}'

In practice, credentials are usually stored in the IntegrationService database (via the UI) rather than environment variables. The ${ENV_VAR} syntax is the fallback when no DB credentials are found.

Endpoint Config Format​

Each endpoint file defines one API operation.

GET Endpoint (Query Parameters)​

endpoint:
name: subscribers-list
description: List all subscribers in your Drip account
path: /subscribers
method: GET
auth: required
quota_cost: 1

query_params:
page:
type: integer
required: false
default: 1
description: Page number for pagination
per_page:
type: integer
required: false
default: 100
max: 1000
description: Number of subscribers per page
status:
type: string
required: false
description: 'Filter by status: active, unsubscribed, removed'
enum:
- active
- unsubscribed
- removed

response:
items_path: subscribers

examples:
active_subscribers:
params:
per_page: 100
status: 'active'

POST Endpoint (Body Parameters)​

endpoint:
name: email-send
description: Send a single transactional email
path: /email
method: POST
auth: required
quota_cost: 1

body_params:
From:
type: string
required: true
description: 'Sender email address'
To:
type: string
required: true
description: 'Recipient email address(es)'
Subject:
type: string
required: true
description: Email subject line
HtmlBody:
type: string
required: false
description: HTML body of the email
TextBody:
type: string
required: false
Tag:
type: string
required: false
description: Tag for categorizing the email

response:
notes: |
Returns on success:
- MessageID: Unique ID for tracking
- SubmittedAt: Timestamp
- ErrorCode: 0 on success

examples:
send_html_email:
body:
From: 'Team <[email protected]>'
To: '[email protected]'
Subject: 'Welcome aboard'
HtmlBody: '<h1>Welcome!</h1>'

Path Parameters​

endpoint:
name: records-list
description: Query records of a specific object type
path: /objects/{object}/records/query
method: POST
auth: required

path_params:
object:
type: string
required: true
description: 'Object slug or ID (e.g., people, companies, deals)'

response:
items_path: data

examples:
list_people:
params:
object: 'people'

Pagination Config​

pagination:
type: nextPageToken
next_token_path: nextPageToken
items_path: items

Supported pagination types:

  • nextPageToken — Token-based pagination (Google APIs, etc.)
  • offsetLimit — Offset/limit pagination
  • linkHeader — HTTP Link header pagination

Endpoint Config Reference​

FieldTypeDescription
endpoint.namestringUnique endpoint identifier
endpoint.descriptionstringHuman-readable description
endpoint.pathstringURL path (supports {param} interpolation)
endpoint.method'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'HTTP method
endpoint.auth'required' | 'optional' | 'none'Authentication requirement
endpoint.quota_costnumberAPI quota units consumed per call
query_params.{name}.typestringParameter type
query_params.{name}.requiredbooleanIs the parameter required?
query_params.{name}.defaultanyDefault value
query_params.{name}.descriptionstringParameter description
query_params.{name}.enumstring[]Allowed values
query_params.{name}.minnumberMinimum value
query_params.{name}.maxnumberMaximum value
path_params.{name}.typestringParameter type
path_params.{name}.requiredbooleanIs the parameter required?
path_params.{name}.descriptionstringParameter description
pagination.typestringPagination strategy
pagination.items_pathstringJSON path to items array in response
pagination.next_token_pathstringJSON path to next page token
response.items_pathstringJSON path to extract items from response
response.notesstringDocumentation notes about the response format
examples.{name}.paramsobjectExample query/path parameters
examples.{name}.bodyobjectExample request body

Creating a New Integration​

1. Create the directory​

mkdir ~/sulla/integrations/myservice

2. Create the auth file​

myservice/myservice.v1-auth.yaml:

api:
name: myservice-v1
version: v1
provider: myservice
base_url: https://api.myservice.com/v1

auth:
type: apiKey
client_secret: '${MYSERVICE_API_KEY}'
header: Authorization
token_storage: local
refresh_automatically: false

3. Create endpoint files​

myservice/list-items.v1.yaml:

endpoint:
name: list-items
description: List all items in your account
path: /items
method: GET
auth: required

query_params:
page:
type: integer
required: false
default: 1
limit:
type: integer
required: false
default: 25
max: 100

response:
items_path: data

pagination:
type: offsetLimit
offset_key: page
limit_key: limit
items_path: data

4. Reload integrations​

Integrations are loaded at startup. To reload without restarting, use the IPC command configapi-reload from the Electron renderer, or restart the application.

5. Store credentials​

Use the Sulla UI (Integrations page) to add credentials for your integration, or store them as environment variables matching the ${ENV_VAR} references in the auth file.

Credential Resolution Order​

When an API call is made, credentials are resolved in this order:

  1. Explicit overrides — token or apiKey passed directly in call options
  2. IntegrationService DB — Credentials stored in PostgreSQL via the UI, looked up by (slug, property, accountId)
  3. YAML ${ENV_VAR} references — Environment variable fallback
  4. Empty string — If nothing is found, the credential is empty (likely causing an auth error from the upstream API)

How It All Connects​

YAML files in ~/sulla/integrations/
↓ loaded by IntegrationConfigLoader on startup
↓
ConfigApiClient (one per integration)
↓ resolves credentials from IntegrationService DB or env vars
↓
REST API: POST /v1/integrations/:accountId/:slug/:endpoint/call
↓ model calls this from Python/code
↓
Third-party API (YouTube, GitHub, Postmark, etc.)

See Integrations API for the HTTP endpoint documentation.