Merge Agent Onboarding (Meta-Gateway)
Merge Agent Onboarding — the meta-gateway pattern. Merge stands in front of hundreds of underlying SaaS APIs (Workday, Salesforce, Greenhouse, NetSuite, Notion, Slack, etc.). The agent onboards once at Merge and receives an account_token that fans out across many downstream APIs via Merge's Linked Account model. Distinct from the other adapter shapes because the credential covers the meta-platform, not a single API. Runtime policy enforcement (signature verify, consent check, scope classify, audit emit) lives in the orchestration.steps below — each step that gates issuance carries on_failure: deny. Lint-time validation of this capability shape lives in the companion Polychro ruleset at https://github.com/api-evangelist/posts/blob/main/polychro/agent-onboarding-rules.yaml — Polychro is Naftiko's governance layer, separate from the capability spec, and is the correct home for cross-object consistency rules that apply across every agent-onboarding capability.
What You Can Do
MCP Tools
agent-register
agent-revoke
Capability Spec
naftiko: 1.0.0-alpha2
info:
label: Merge Agent Onboarding (Meta-Gateway)
description: 'Merge Agent Onboarding — the meta-gateway pattern. Merge stands in front of
hundreds of underlying SaaS APIs (Workday, Salesforce, Greenhouse, NetSuite, Notion,
Slack, etc.). The agent onboards once at Merge and receives an account_token that fans
out across many downstream APIs via Merge''s Linked Account model. Distinct from the
other adapter shapes because the credential covers the meta-platform, not a single API. Runtime policy enforcement (signature verify, consent check, scope classify, audit emit) lives in the orchestration.steps below — each step that gates issuance carries on_failure: deny. Lint-time validation of this capability shape lives in the companion Polychro ruleset at https://github.com/api-evangelist/posts/blob/main/polychro/agent-onboarding-rules.yaml — Polychro is Naftiko''s governance layer, separate from the capability spec, and is the correct home for cross-object consistency rules that apply across every agent-onboarding capability.'
tags:
- Merge.dev
- Unified API
- Meta-Gateway
- Linked Accounts
- Agent Onboarding
- LLM Gateway
- Agent Handler
- Naftiko Capability
created: '2026-05-28'
modified: '2026-05-28'
related:
- https://apievangelist.com/2026/05/27/automated-agent-onboarding-is-a-naftiko-capability-not-a-gateway-feature/
- https://github.com/api-evangelist/merge
binds:
- namespace: env
keys:
MERGE_API_KEY: MERGE_API_KEY
MERGE_ACCOUNT_ID: MERGE_ACCOUNT_ID
AGENT_TRUSTED_ISSUERS: AGENT_TRUSTED_ISSUERS
AGENT_CONSENT_HASH: AGENT_CONSENT_HASH
capability:
consumes:
- type: http
namespace: merge-api
baseUri: https://api.merge.dev/api
description: Merge Unified API — Linked Account provisioning, account-token exchange,
integration discovery, and linked-account scope adjustments.
resources:
- name: create-link-token
path: /integrations/create-link-token
operations:
- name: createlinktoken
method: POST
description: Create the magic-link / DCR-shaped initialization token bound to the
requesting account. The agent receives the link_token in the onboarding response
and exchanges it for an account_token in the next step.
outputRawFormat: json
outputParameters:
- { name: result, type: object, value: $. }
inputParameters:
- { name: body, in: body, type: object, required: true }
- name: account-token
path: /integrations/account-token/{public_token}
operations:
- name: getaccounttoken
method: GET
description: Exchange the public link token for a permanent account_token (the
credential the agent uses for all subsequent Unified API calls).
outputRawFormat: json
outputParameters:
- { name: result, type: object, value: $. }
inputParameters:
- { name: public_token, in: path, type: string, required: true }
- name: integrations
path: /integrations
operations:
- name: listintegrations
method: GET
description: Discover which downstream SaaS providers are available to the agent
once Linked.
outputRawFormat: json
outputParameters:
- { name: result, type: object, value: $. }
- name: linked-accounts
path: /integrations/linked-accounts/{id}
operations:
- name: patchlinkedaccount
method: PATCH
description: Adjust scope or metadata on the linked account post-creation.
outputRawFormat: json
outputParameters:
- { name: result, type: object, value: $. }
inputParameters:
- { name: id, in: path, type: string, required: true }
- { name: body, in: body, type: object, required: true }
- name: deletelinkedaccount
method: DELETE
description: Revoke the agent's linked account; all downstream SaaS access is
severed simultaneously.
outputRawFormat: json
outputParameters:
- { name: result, type: object, value: $. }
authentication:
type: bearer
token: '{{env.MERGE_API_KEY}}'
orchestration:
- name: onboard-agent
description: Verify Web Bot Auth, classify scopes against Merge's unified vocabulary,
create a link_token bound to the agent's operator account, exchange for account_token,
annotate the linked account with the agent identity metadata, emit audit via Merge's
own logging surface.
inputs:
- { name: signature, type: object, required: true }
- { name: signature_agent, type: string, required: true }
- { name: skill_id, type: string, required: true }
- { name: requested_scopes, type: array, required: true, description: 'Merge unified scopes — e.g. read:hris.employees, write:accounting.invoices.' }
- { name: end_user_identifier, type: string, required: true, description: 'Merge requires an end-user identifier even for agent flows; typically the operator''s account-level identifier.' }
- { name: consent_hash, type: string, required: true }
- { name: contact, type: object, required: true }
steps:
- id: verify_signature
type: builtin.web-bot-auth.verify
with:
signature: '${input.signature}'
agent: '${input.signature_agent}'
trusted_issuers: '{{env.AGENT_TRUSTED_ISSUERS}}'
on_failure: deny
- id: verify_consent
type: builtin.policy.assert
with:
assert: '${input.consent_hash} == {{env.AGENT_CONSENT_HASH}}'
on_failure: deny
- id: classify_scopes
type: builtin.policy.scope-classify
description: Map Merge unified scopes to (auto-issue | approval-required | forbidden)
per the provider's declared policy.
with:
requested: '${input.requested_scopes}'
- id: create_link_token
call: merge-api.createlinktoken
with:
body:
end_user_origin_id: '${input.end_user_identifier}'
end_user_organization_name: '${input.contact.operator}'
end_user_email_address: 'agent-${steps.verify_signature.agent_id}@${input.contact.operator}'
categories: '${steps.classify_scopes.merge_categories}'
link_expiry_mins: 30
- id: exchange_for_account_token
call: merge-api.getaccounttoken
with:
public_token: '${steps.create_link_token.link_token}'
- id: annotate_linked_account
call: merge-api.patchlinkedaccount
description: Attach the Web Bot Auth signature metadata + consent hash to the
linked account so subsequent audit queries can attribute traffic.
with:
id: '${steps.exchange_for_account_token.id}'
body:
custom_metadata:
naftiko_agent_id: '${steps.verify_signature.agent_id}'
naftiko_operator: '${input.contact.operator}'
naftiko_consent_hash: '${input.consent_hash}'
naftiko_signature_keyid: '${steps.verify_signature.keyid}'
naftiko_skill_id: '${input.skill_id}'
- id: emit_audit
type: builtin.audit.emit
description: Merge's own audit/logging picks up the per-vendor calls. The orchestration
emits a synthetic onboarding event tagged with the agent identity for joinability.
with:
target: merge-api.integrations
custom_event:
event_type: agent.onboarded
agent_id: '${steps.verify_signature.agent_id}'
operator: '${input.contact.operator}'
merge_categories: '${steps.classify_scopes.merge_categories}'
linked_account_id: '${steps.exchange_for_account_token.id}'
consent_hash: '${input.consent_hash}'
output:
agent_id: '${steps.verify_signature.agent_id}'
linked_account_id: '${steps.exchange_for_account_token.id}'
credential:
type: Bearer
header: Authorization
value: '${steps.exchange_for_account_token.account_token}'
revocation_url: '/v1/agents/${steps.verify_signature.agent_id}/revoke'
merge_categories: '${steps.classify_scopes.merge_categories}'
reachable_integrations: '${steps.classify_scopes.merge_categories}'
- name: revoke-agent
description: Delete the linked account; severs access to all downstream SaaS integrations
in one call.
inputs:
- { name: linked_account_id, type: string, required: true }
steps:
- id: delete_la
call: merge-api.deletelinkedaccount
with:
id: '${input.linked_account_id}'
output:
revoked: true
exposes:
- type: rest
namespace: merge-agent-onboarding-rest
port: 8080
resources:
- path: /v1/agents/onboard
operations:
- { method: POST, name: onboardagent, call: orchestration.onboard-agent }
- path: /v1/agents/{agent_id}/revoke
operations:
- { method: POST, name: revokeagent, call: orchestration.revoke-agent }
- type: mcp
namespace: merge-agent-onboarding-mcp
port: 9090
transport: http
tools:
- name: agent-register
hints: { readOnly: false, destructive: false, idempotent: false }
call: orchestration.onboard-agent
- name: agent-revoke
hints: { readOnly: false, destructive: true, idempotent: true }
call: orchestration.revoke-agent
- type: agent-skill
namespace: merge-agent-onboarding-skill
description: 'Agent skill at /skills/onboard-agent.md. Merge-specific addendum:
one onboarding call yields a credential the agent uses for every downstream SaaS Merge
proxies to; the agent does not need to know which underlying SaaS is behind any
given Unified API endpoint.'
skill:
name: onboard-agent
file: skills/onboard-agent.md