Integrations Object
Properties
The configuration object defining the underlying API of the integration.
{
"base_url": "https://api.example.com",
"label": "Example API Integration",
"logo": "https://example.com/logo.png",
"icon": "https://example.com/icon.png",
"headers": {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "truto"
},
"query": {
"search": "{{search_query}}",
"filter": "{{filter_criteria}}"
},
"query_array_format": "comma",
"actions": {
"sync_users": {
"type": "request",
"config": {
"method": "post",
"path": "/sync/users",
"headers": {
"Authorization": "Bearer {{oauth.token.access_token}}"
},
"body": {
"users": "{{context.users}}"
}
}
}
},
"credentials": {
"oauth2": {
"format": "oauth2",
"config": {
"client": {
"id": "your-client-id",
"secret": "your-client-secret"
},
"auth": {
"tokenHost": "https://auth.example.com",
"tokenPath": "/oauth/token",
"refreshPath": "/oauth/refresh"
},
"options": {
"scopeSeparator": " ",
"authorizationMethod": "header",
"bodyFormat": "form"
},
"fields": [
{
"name": "client_id",
"label": "Client ID",
"type": "text",
"required": true
},
{
"name": "client_secret",
"label": "Client Secret",
"type": "password",
"required": true
}
],
"tokenParams": {
"grant_type": "client_credentials"
},
"refreshParams": {
"grant_type": "refresh_token"
},
"tokenExpiryDuration": "3600"
}
}
},
"authorization": {
"format": "bearer",
"config": {
"token": "{{oauth.token.access_token}}"
}
},
"pagination": {
"format": "page",
"config": {
"page_key": "page",
"limit_key": "per_page"
}
},
"rate_limit": {
"is_rate_limited": true,
"retry_after_header_expression": "Retry-After",
"rate_limit_header_expression": "X-RateLimit-Remaining"
},
"resources": {
"users": {
"list": {
"method": "get",
"path": "/users",
"response_path": "data.users",
"headers": {
"Authorization": "Bearer {{oauth.token.access_token}}"
},
"query": {
"page": "{{pagination.page}}",
"per_page": "{{pagination.per_page}}"
},
"pagination": {
"format": "page",
"config": {
"page_key": "page",
"limit_key": "per_page"
}
},
"authorization": {
"format": "bearer",
"config": {
"token": "{{oauth.token.access_token}}"
}
},
"rate_limit": {
"is_rate_limited": true,
"retry_after_header_expression": "Retry-After",
"rate_limit_header_expression": "X-RateLimit-Remaining"
},
"examples": {
"response": "{\n \"data\": {\n \"users\": [\n {\n \"id\": \"123e4567-e89b-12d3-a456-426614174000\",\n \"name\": \"John Doe\",\n \"email\": \"john.doe@example.com\"\n }\n ]\n }\n}\n"
}
}
},
"orders": {
"create": {
"method": "post",
"path": "/orders",
"body": {
"user_id": "{{context.user_id}}",
"items": "{{context.items}}"
},
"response_path": "data.order",
"headers": {
"Authorization": "Bearer {{oauth.token.access_token}}"
},
"authorization": {
"format": "bearer",
"config": {
"value": "{{oauth.token.access_token}}"
}
}
}
}
},
"webhook": {
"signature_verification": {
"format": "hmac",
"config": {
"secret": "{{context.environment_variables.WEBHOOK_SECRET}}",
"algorithm": "SHA-256",
"string_type": "hex",
"compare_with": "{{headers.x-signature}}",
"parts": [
"raw_body"
]
}
},
"handle_verification": "{ 'type': webhook_type = 'verify' ? 'verify' : 'payload', 'verification_response': webhook_type = 'verify' ? { 'body': { 'challenge': body.challenge } } }"
},
"error_expression": "status >= 400 ? { 'status': status, 'message': data.error.message }"
}Named integration actions. Reserved keys (post_install, post_connect_user_form, refresh_token, validation) hook into specific platform lifecycle events; custom names are callable from the proxy/sync runtime.
How Truto applies the resolved credential to outbound HTTP requests. The format discriminator selects which config shape applies.
All string values support Truto placeholders ({{path}}) resolved against the runtime context. Common placeholder roots: {{api_key}} for fields collected at connect time, {{oauth.token.access_token}} for OAuth2 access tokens, {{environment_variables.MY_KEY}} for env-vars set at the environment-integration level.
bearer · 2 properties
1 property
The bearer token. Almost always a placeholder like {{oauth.token.access_token}} (OAuth2) or {{api_key}} (api-key fields).
{{oauth.token.access_token}}bearer
basic · 2 properties
2 properties
Password, supports placeholders.
{{client_secret}}Username, supports placeholders (e.g. {{api_key}} for vendors that pass the API key as the username).
{{client_id}}basic
header · 2 properties
2 properties
JSONata expression that fully constructs the outgoing request. Use for advanced cases (request signing, dynamic URL rewrites).
Input: IntegrationHeaderAuthorizationContext. Output: IntegrationHeaderAuthorizationResult (must contain url and requestOptions).
Map of header name → value. Values support placeholders (e.g. { "X-API-Key": "{{api_key}}" }).
{"X-API-Key":"{{api_key}}","X-Account-Id":"{{account_id}}"}header
Default base URL prepended to every resource method's path.
https://api.example.comThis provider's browser file picker cannot run without the connected account's upstream access_token, so GET /integrated-account/me releases context.oauth.token.access_token — and nothing else — to a connect-flow integrated account token. Google Drive and Box need it; their picker SDKs take the token client-side. SharePoint, OneDrive and Dropbox authenticate by other means and must leave this unset.
A refresh_token is never released, whatever this is set to. Honoured only on the integration's own config: it is not part of EnvironmentIntegrationConfig, and it is stripped from an integrated account's integration_override, so it cannot be switched on for a single environment or account.
Either a single credential definition (when the integration only supports one auth format) or a map keyed by auth format (when an integration supports multiple, e.g. api_key and oauth2). For multi-format integrations, the customer picks one in the Connect UI. Each credential's config.label and config.help_text customize the auth-method card shown in the Connect UI. A jwt_bearer or aws_sts credential must be keyed by its format (credentials.jwt_bearer, credentials.aws_sts): sent as the single definition, it is refused with a 400.
Integration-wide JSONata expression evaluated on every response. Use to detect errors in successful (2xx) responses, normalize error messages, or transform "errors" with < 400 status into successful responses. Overridden by per-method IntegrationResourceMethod.error_expression when set.
Input: IntegrationErrorExpressionContext. Output: IntegrationErrorExpressionResult — return null/undefined to indicate "no error", or a { status, message?, headers?, metadata?, result? } object.
Default HTTP headers merged into every outbound request. Values may be templated with JSONata placeholders.
URL to a smaller monochrome icon used in catalog listings.
Human-readable name shown in the Truto Dashboard and Link UI.
Example APIURL to the integration logo (square, recommended 256x256).
Headers built from the connected account's OAuth app and sent on every request, for providers that want the app's own key next to the user's token. Etsy, for example, needs { "x-api-key": "{{client.id}}:{{client.secret}}" }.
A value may use {{client.id}} and {{client.secret}} — the OAuth client the account's token was issued to, read and filled in as the token refresh does — and no other placeholder. Saving an integration fails with a 400 if a value uses any other placeholder, or if a header name is one that debug output echoes back (x-truto-*, content-type, accept, user-agent and similar).
A header that can't be filled completely is left out: a client field it uses is unset, or is still a placeholder the account's context can't fill. Added after authorization, replacing a header of the same name in any letter case. Only accounts connected with oauth2 get these headers, and a method with no_auth doesn't.
Honoured only on the integration's own config: it is not part of EnvironmentIntegrationConfig, and it is stripped from an integrated account's integration_override.
A header that uses {{client.secret}} is sent only to the sites of the requests configured by whoever set that secret: the integration, or an environment's override over it. An environment or account override that keeps that app but sends requests elsewhere (its own base_url, a method's URL, an authorization expression, a mapping's path) sends them without these headers. Through a Static Gate, the target URL counts. An account's own secret (in its integration_override, or filled in from its context) goes wherever its requests go.
Pagination strategy for an integration or a single resource method. The format discriminator selects which config shape applies.
page · 2 properties
Page-number pagination options. All fields optional; defaults shown.
9 properties
Add pagination keys to the request body instead of the query string.
Add pagination keys to the query string. Defaults to true when add_to_body is false.
Static or templated headers added to every paginated request. Templated against the request query plus a synthetic limit.
Drop the limit_key from the URL after pagination is applied (for APIs that 400 on it).
Query/body key that holds the page size.
Maximum page size Truto will request, regardless of the caller's limit.
Query/body key that holds the page number.
Page number used for the first page (some APIs are 0-indexed).
Stop paginating when consecutive pages return identical results (loop detection). Defaults to true.
page
cursor · 2 properties
Cursor pagination options.
17 properties
Send the cursor in the request body instead of the URL.
Add the cursor to the query string.
Don't treat an empty cursor as the end of pagination.
Query/body key Truto uses to send the cursor on the next request.
Dotted-path accessor into the response body (or headers, when cursor_path_object: header) that yields the next cursor.
data.meta.next_cursorWhere to read cursor_path from.
bodyheader
Optional accessor that returns a truthy/"true"/"false" value indicating whether more pages exist.
Static or templated headers added to every paginated request. Templated against the request query plus a synthetic limit.
Don't re-send limit_key after the first page (for APIs that reject it on subsequent calls).
When is_link is true, also overwrite the request path with the link's path (not just the query string).
URL-decode the cursor before sending (some upstreams double-encode).
Treat the value at cursor_path as a fully-qualified URL (or path) rather than an opaque token.
Cast the limit value when sending it in the body.
numberstring
Query/body key that holds the page size.
Maximum page size Truto will request.
Optional accessor that yields the previous-page cursor.
Detect loops by hashing each page's results. Only effective when cursor_path_object is body.
cursor
link_header · 2 properties
5 properties
Static or templated headers added to every paginated request.
Don't send limit_key even when the caller sets limit.
Query key that holds the page size when the caller sets limit.
Maximum page size Truto will request.
Informational only; the actual cursor comes from the Link header.
link_header
offset · 2 properties
10 properties
Send pagination params in the request body instead of the query string.
Send pagination params in the query string.
Offset used for the first request (some APIs are 1-indexed).
Static or templated headers added to every paginated request.
Drop limit_key from the URL after pagination is applied.
Query/body key that holds the page size.
Maximum page size Truto will request.
Query/body key that holds the offset.
Optional dotted-path accessor into the response body that yields the total result count, used to stop paginating early.
Detect loops by hashing each page's results.
offset
range · 2 properties
9 properties
Send pagination params in the request body instead of the query string.
Send pagination params in the query string.
Start value used for the first request.
Query/body key that holds the end of the range (exclusive).
Static or templated headers added to every paginated request.
Drop start_key, end_key, and limit from the URL after pagination is applied.
Maximum range size Truto will request.
Query/body key that holds the start of the range.
Optional dotted-path accessor into the response body that yields the total result count, used to stop paginating early.
range
dynamic · 2 properties
3 properties
JSONata expression evaluated after each response. Returns the next cursor (Truto then base64-encodes it) and optional loop-detection flags.
Input: IntegrationDynamicPaginationResponseContext. Output: IntegrationDynamicPaginationResponseResult.
Optional JSONata expression evaluated before the first request. Use to seed pagination values (e.g. compute a default since window).
Input: IntegrationDynamicPaginationRequestContext. Output: IntegrationDynamicPaginationRequestResult.
JSONata expression evaluated before each subsequent request (when query.next_cursor is set). Mutates the URL/body/headers/query for the next page.
Input: IntegrationDynamicPaginationRequestContext with query.next_cursor decoded from base64. Output: IntegrationDynamicPaginationRequestResult.
dynamic
Default query-string params merged into every outbound request.
commabracketsindicesrepeat
How Truto detects and reacts to upstream rate-limiting. All fields are JSONata expressions evaluated against the upstream response.
3 properties
JSONata expression returning a truthy value when the response should be treated as rate-limited. When omitted, Truto falls back to status === 429.
Input: IntegrationRateLimitExpressionContext. Output: boolean.
status = 429JSONata expression returning the current rate-limit window state. Truto forwards this as RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers in the proxy response.
Input: IntegrationRateLimitExpressionContext. Output: IntegrationRateLimitHeaderValues.
{ "limit": headers.`x-ratelimit-limit`, "remaining": headers.`x-ratelimit-remaining`, "reset": headers.`x-ratelimit-reset` }
JSONata expression returning the seconds to wait before retrying. When omitted, Truto reads the standard Retry-After header.
Input: IntegrationRateLimitExpressionContext. Output: number (seconds).
$number(headers.`retry-after`)Resource → method tree (e.g. resources.users.list). The inner key is one of the canonical methods (list, get, create, update, delete) or a custom method name. Each method definition matches IntegrationResourceMethod.
Optional tag arrays keyed by resource name (contacts) or
resource.method (contacts.delete). Method-level tags are unioned
with resource-level tags when building MCP tools and filtering by
MCP token config.tags.
Send every outbound request and token exchange for this integration through Truto's default Static Gate (fixed-IP proxy, configured on the worker), for vendors that allowlist Truto's IPs. An integrated account's own context.truto_static_gate_id takes precedence. If the flag is set but the worker has no gate configured, the request fails instead of going direct.
The default gate only forwards to the host of the base integration's base_url and of its OAuth token, refresh and revoke URLs, and their subdomains (a placeholder in the host keeps only its static suffix, e.g. {{x}}.api.gong.io allows *.api.gong.io). A base_url or token host set through an override, or any other host, fails the request instead of going direct. OAuth 1.0a token calls cannot be sent through a gate, so an OAuth 1.0a connect on an integration with this flag fails instead of going direct.
Only the root team can set, change or clear it, and a config update that omits it keeps the stored value. Honoured only on the integration's own config: it is not part of EnvironmentIntegrationConfig, and it is stripped from an integrated account's integration_override.
How Truto receives and verifies inbound webhooks for this integration. Not to be confused with WebhookSchema elsewhere in this spec, which describes Truto's outbound webhook delivered to your application. An inbound webhook whose body is larger than 1 MiB is refused with a 413.
4 properties
JSONata expression evaluated on every inbound webhook before signature verification. Use to:
- Respond to handshake pings (return
{ type: 'verify', verification_response: { status_code, body, headers } }). - Surface meta-events that should not fan out (return
{ type: 'meta' }). - Update the integrated account context (return
{ type: 'verify' \| 'payload' \| 'meta', update_context: { ... } }). - Pass through to fan-out (return
{ type: 'payload' }or omit entirely — defaults topayload).
Input: IntegrationWebhookPayloadContext. Output: IntegrationWebhookHandleVerificationResult.
JSONata expression that transforms the raw inbound payload before handle_verification and signature_verification see it. Use to normalize vendor-specific envelopes (e.g. unwrap a Salesforce payloads[] array, decode a base64 body).
Input: IntegrationWebhookPayloadContext. Output: IntegrationWebhookPayloadContext (the same shape — what you return becomes the new payload).
Strategy used to verify the webhook signature before accepting it. Runs after payload_transform and after handle_verification returns { type: 'payload' }.
hmac · 2 properties
HMAC signature verification. Truto computes hmac(algorithm, secret, signed_string) and compares it (constant-time) with compare_with.
6 properties
Hash algorithm, as a WebCrypto name in any case: SHA-1, SHA-256 (the default), SHA-384 or SHA-512. A name without the hyphen, such as sha256, fails every webhook.
SHA-256The signature value from the request to compare against. Usually a placeholder like {{headers.x-signature}}.
The IntegrationWebhookPayloadContext field that was signed, e.g. ["raw_body"] or ["body"]. Only one field is signed: if several are listed, the last one the payload has. For body or raw_body, a signature over the raw request body is accepted, and so is one over the parsed body serialized again with JSON.stringify. Any other field is signed as its JSON.stringify value. To sign several values joined together, such as a timestamp and the body, use verification_content. Mutually exclusive with verification_content.
Shared HMAC secret. Typically a placeholder like {{context.webhook_secret}} or {{context.environment_variables.WEBHOOK_SECRET_secret}}. An empty secret, or a placeholder that does not resolve, fails verification. A placeholder filled with :json may give an object of secrets instead, and a signature made with any one of them passes: FreshBooks keeps its verifiers by callback id in {{context.webhook_secret:json}}, because an event does not say which callback sent it. Every value must be a usable secret, and an empty object fails verification.
Output encoding for the computed digest.
hexOrdered list of placeholders to concatenate as the signed string (e.g. ["{{headers.x-timestamp}}", ".", "{{raw_body}}"]). Mutually exclusive with parts.
hmac
basic · 2 properties
HTTP Basic signature verification. Truto base64-encodes username:password and compares it with the second word of compare_with (i.e. Authorization: Basic <token>).
3 properties
The full Authorization header value, e.g. {{headers.authorization}}.
Expected password. Supports placeholders.
Expected username. Supports placeholders.
basic
bearer · 2 properties
Bearer-token signature verification. Truto compares secret (constant-time) with the second word of compare_with (i.e. Authorization: Bearer <token>).
2 properties
The full Authorization header value, e.g. {{headers.authorization}}.
Expected bearer token. Supports placeholders.
bearer
jwt · 2 properties
JWT signature verification, in one of two modes.
Shared secret (set secret): Truto verifies that compare_with is a valid HS256 JWT signed with secret. When every connection shares the secret, such as an OAuth app's client secret, a valid token only proves the provider sent it for that app; set claims to tie it to the connection.
Issuer key set (set jwks_uri): Truto verifies that compare_with is a JWT signed with an asymmetric key published at jwks_uri, then checks issuer, audience and the caller's identity. Use this for tokens such as the OIDC token on a Google Pub/Sub push subscription (jwks_uri: https://www.googleapis.com/oauth2/v3/certs, issuer: https://accounts.google.com). The issuer, audience and one of email or subject are all required, because the issuer signs a token for any caller who asks and a valid signature alone does not identify the sender. A block that omits them, uses a non-HTTPS jwks_uri, or sets a non-asymmetric algorithm is rejected with a 400 when it is saved (on the integration or on an environment-integration override), instead of failing every inbound webhook afterwards.
9 properties
Asymmetric algorithm the token must use (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512 or EdDSA). Defaults to RS256. Used with jwks_uri only.
Required aud claim. Supports placeholders, e.g. https://api.truto.one/environment-integration-webhook/{{environment_integration_id}}. Used with jwks_uri only.
Claims the token must carry, in either mode. Each key is a claim path (dots for nested claims), and each value is the exact text that claim must equal. Values are trust settings, like secret. A value that is empty, or still holds a placeholder that did not resolve, fails every webhook with a 400. Atlassian names the site in context.cloudId, so {"context.cloudId": "{{context.cloud_id}}"} ties its tokens to the connection's site. That is as far as a claim ties a token: a token for another connection to the same site still passes, and nothing in Atlassian's token covers the body or URL, so until it expires (15 minutes) it can be sent again with another body.
The JWT to verify, typically a placeholder like {{headers.authorization}} or {{body.token}}. With jwks_uri, an Authorization: Bearer <jwt> value is accepted as-is.
Required email claim, e.g. the push subscription's service account. The token's email_verified must also be true. Used with jwks_uri only.
Required iss claim, e.g. https://accounts.google.com. Used with jwks_uri only.
HTTPS URL of the issuer's JSON Web Key Set. Setting it selects the issuer-key-set mode. Keys are cached for an hour.
Shared signing secret for HS256. Supports placeholders. Not used when jwks_uri is set.
Required sub claim. Use instead of, or together with, email. Used with jwks_uri only.
jwt
Authentication methods (api_key, oauth2, oauth2_client_credentials, keka_oauth, oauth, jwt_bearer, aws_sts) that skip the signature check on the per-account webhook URL (/integrated-account-webhook/:id) and are accepted the same way a webhook with no signature verification configured always has been — including the unverified-webhook log marker. Checked against the connection's own authentication_method. A connection that stores none (older connections) counts as oauth2 when it holds an OAuth token, as api_key when its context holds an api_key field instead (as Jira's and monday's API-key connections do), and is checked when it holds neither. Ignored on the environment-level webhook URL, which has no connection.
For integrations where some authentication methods can carry a signature and others cannot (e.g. a provider that signs OAuth connections but not static api_key ones), list only the methods that cannot, rather than leaving signature_verification unset for every connection.
api_keyoauth2keka_oauthoauth2_client_credentialsoauthjwt_beareraws_sts
The category of the integration.
helpdeskThe date and time when the integration was created.
2021-08-10T10:00:00.000ZThe ID of the integration.
4a4de828-f4db-4c9e-adfd-434e0864c3c7A list of environment IDs where this integration is installed.
Whether the integration is in beta or not. Beta integrations might not have been tested completely and are not recommended for production environments.
falseThe name of the integration.
zendeskThe sharing policy of the integration.
allowallowaskdeny
Whether the team allows impersonation.
falseThe date and time when the team was created.
2021-08-10T10:00:00.000ZThe domain of the team.
example.comThe ID of the team.
05daecaf-4365-42e8-8370-8127de5dd717Whether the team is verified or not.
trueWhether the team is white-labeled or not.
falseThe URL of the team's logo.
https://example.com/logo.pngThe name of the team.
My Awesome TeamA link to the team's Terms of Service, if available.
https://example.com/tosThe date and time when the team was last updated.
2021-08-10T10:00:00.000ZThe ID of the team that owns this integration.
05daecaf-4365-42e8-8370-8127de5dd717The date and time when the integration was last updated.
2021-08-10T10:00:00.000Z