Siphondocs
Connector Spec

Auth

Nine authentication strategies — static keys, HMAC/RSA/ECDSA JWTs, OAuth2, and custom login exchanges.

auth is a discriminated union on strategy. Static strategies template credentials directly into each request; token strategies exchange for a bearer token, cache it until expiry, and re-mint automatically. Every strategy resolves inside the guarded HTTP stack, so credentials are redacted from logs.

Injection

Token strategies (and bearer_static) share an inject block controlling where the token goes. The default is Authorization: Bearer {{ token }}.

inject:
  type: header        # header | query
  name: Authorization
  format: "Bearer {{ token }}"

Token strategies also share token extraction fields:

FieldDefaultPurpose
tokenPath$.access_tokenJSONPath to the token in the exchange response.
expiresInPathJSONPath to a TTL (seconds); enables auto-refresh.
refreshSkewSeconds120Re-mint this many seconds before expiry.

The strategies

bearer_static

A static bearer token (usually from a secret).

auth:
  strategy: bearer_static
  token: "{{ secrets.apiToken }}"

basic

HTTP Basic — username:password, base64-encoded.

auth: { strategy: basic, username: "{{ secrets.user }}", password: "{{ secrets.pass }}" }

api_key_header / api_key_query / api_key_body

An API key placed in a header, a query param, or a top-level JSON body field. The header name itself is templatable, so it can be a per-connection param.

auth:
  strategy: api_key_header
  apiKey: "{{ secrets.apiKey }}"
  header: "{{ params.apiKeyHeader }}"   # e.g. "X-API-Key"
  format: "{{ apiKey }}"                # optional wrapper

# or
auth: { strategy: api_key_query, apiKey: "{{ secrets.apiKey }}", queryParam: api_key }
auth: { strategy: api_key_body,  apiKey: "{{ secrets.apiKey }}", field: api_key }

jwt

Mint a signed JWT per request. Supports HMAC (HS256/384/512), RSA (RS256/384/512), and ECDSA (ES256/384/512 — emitted as JOSE raw R‖S).

auth:
  strategy: jwt
  algorithm: RS256
  secret: "{{ secrets.privateKeyPem }}"
  claims: { sub: "{{ params.clientId }}" }
  issuer: my-app
  expiresInSeconds: 300

oauth2_client_credentials

Machine-to-machine OAuth2. Exchanges client_id/client_secret for a token and caches it.

auth:
  strategy: oauth2_client_credentials
  tokenUrl: "{{ params.baseUrl }}/oauth/token"
  clientId: "{{ secrets.clientId }}"
  clientSecret: "{{ secrets.clientSecret }}"
  scopes: [read]
  tokenPath: "$.access_token"
  expiresInPath: "$.expires_in"

custom_token_exchange

For vendors with a bespoke login endpoint — post credentials, read the token out of the response, cache and re-mint on expiry. The request body is deep-templated.

auth:
  strategy: custom_token_exchange
  request:
    method: POST
    url: "{{ params.baseUrl }}/api/Auth/login"
    headers: { content-type: application/json }
    body:
      email: "{{ secrets.email }}"
      password: "{{ secrets.password }}"
  tokenPath: "$.accessToken"
  expiresInPath: "$.expiresIn"
  refreshSkewSeconds: 60
  inject: { type: header, name: Authorization, format: "Bearer {{ token }}" }

oauth2_authorization_code

Interactive three-legged OAuth (authorize → callback → exchange → refresh). The control plane drives the browser flow and stores the token; at sync time the engine loads that already-authorized access token (refreshing it via the stored refresh token if expired) and injects it — so an OAuth-AC connection syncs like any other. A connection that hasn't completed the connect flow fails the run with a clear "authorize first" error.

Strategy reference

strategyKindKey fields
bearer_staticstatictoken, inject?
basicstaticusername, password
api_key_headerstaticapiKey, header, format?
api_key_querystaticapiKey, queryParam
api_key_bodystaticapiKey, field
jwtmintedalgorithm, secret, claims, issuer?, expiresInSeconds?
oauth2_client_credentialstokentokenUrl, clientId, clientSecret, scopes?
custom_token_exchangetokenrequest{method,url,headers?,body?}, tokenPath
oauth2_authorization_codetokenauthorizationUrl, tokenUrl, clientId, clientSecret

Secrets are only in scope inside auth — you cannot leak {{ secrets.* }} into a fetch URL or transform.

On this page