Delivery targets a run's output can be saved to, beyond the default download.
By default, a run's output is just a downloadable artifact (GET /v1/runs/:id/artifacts/:artifactId). A connection is a workspace-configured delivery target the save_output tool can also deliver to — email, an outgoing webhook, S3-compatible object storage, or a folder on an SFTP server:
| Provider | Status |
|---|---|
email |
Available |
webhook |
Available |
s3 |
Available — any S3-compatible endpoint (AWS S3, Cloudflare R2, MinIO, ...) |
sftp |
Available — a folder on an SFTP server, with a password or a private key (below) |
gdrive |
Not yet implemented — 501 |
sharepoint |
Not yet implemented — 501 |
A connection only ever sends results out. Reading documents and tables from a customer's systems is the other direction — see Data sources, which are read-only.
curl -X POST "https://api.agentflowbind.com/v1/connections" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{
"provider": "s3",
"name": "Finance archive bucket",
"config": {
"endpoint": "https://s3.eu-central-1.amazonaws.com",
"bucket": "afb-finance-archive",
"region": "eu-central-1",
"accessKeyId": "AKIA...",
"secretAccessKey": "..."
}
}'{
"id": "7d2e9f10-...",
"provider": "s3",
"name": "Finance archive bucket",
"status": "active",
"metadata": { "endpoint": "https://s3.eu-central-1.amazonaws.com", "bucket": "afb-finance-archive", "region": "eu-central-1" },
"createdAt": "2026-03-14T09:00:00.000Z",
"updatedAt": "2026-03-14T09:00:00.000Z"
}Secret fields (accessKeyId/secretAccessKey for s3, a webhook's signing secret, an sftp password, private key and passphrase) are split out and sealed at rest — they never come back in a GET/POST response, only the non-secret metadata. Trying gdrive/sharepoint today returns 501 with "code": "storage.not_implemented".
An sftp connection writes into one folder of an SFTP server — typically the output folder next to the inbox a data source reads from:
curl -X POST "https://api.agentflowbind.com/v1/connections" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{
"provider": "sftp",
"name": "Invoices · processed",
"config": {
"host": "sftp.example.com",
"port": 22,
"user": "afb",
"privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
"directory": "/fatture/processed"
}
}'host, user and directory are required. directory is the folder the files are written into; it is created on the first delivery if it is missing.password or privateKey is required; passphrase only goes with privateKey. All three are sealed.port is optional, 22 by default.A delivered file is never overwritten: its name is sanitised (no path separators, no ..), and when a file of that name is already in the folder the new one is saved as name-2.csv, name-3.csv, and so on. The delivery's reference is sftp://user@host:port/<directory>/<name> — host, port, user and path only, never a credential.
POST /v1/connections/:id/test performs a non-destructive connectivity/credential check (e.g. an S3 HeadBucket, verifying an email sender is configured, or — for sftp — logging in and checking that directory is there and is a folder) without writing anything:
curl -X POST "https://api.agentflowbind.com/v1/connections/7d2e9f10-.../test" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"{ "ok": true, "message": "Connected to afb@sftp.example.com:22 — the folder \"/fatture/processed\" is there." }A check that fails — a refused connection, wrong credentials, a missing folder — is still a 200, with "ok": false and the reason in message.
Pass the connection's id as input.fields.saveTarget when creating a run; every document preset's save_output tool step reads it and delivers there in addition to writing the run's own output artifact:
curl -X POST "https://api.agentflowbind.com/v1/runs" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{
"agentId": "3c1a9e40-...-77bd",
"input": { "fileIds": ["b4a0c9e1-...-1f0a"], "fields": { "saveTarget": "7d2e9f10-..." } }
}'An agent can carry a default target (config.defaultSaveTarget), which the console's new-run wizard pre-selects; a run still delivers where its own input.fields.saveTarget says.
When the run also asks for a report ("outputConfig": { "method": "report" }), the rendered report PDF follows to the same connection once the run has succeeded — best-effort: a failed report delivery never fails the run.
Every delivery is recorded once the run has succeeded. GET /v1/runs/:id returns them in deliveries, and the console's run page shows them under Delivered to (Consegnato in):
{
"deliveries": [
{ "source": "save_output", "filename": "2026_FT-0184-extracted.json", "ref": "sftp://afb@sftp.example.com:22/fatture/processed/2026_FT-0184-extracted.json", "status": "delivered", "error": null, "...": "..." },
{ "source": "report", "filename": "fatture-fornitori-acme.pdf", "ref": "sftp://afb@sftp.example.com:22/fatture/processed/fatture-fornitori-acme.pdf", "status": "delivered", "error": null, "...": "..." }
]
}source is save_output (the agent's own file, delivered during the run) or report; status is delivered or failed (with error). GET /v1/runs/:id/steps?include=io still shows each save_output step's own output.
curl "https://api.agentflowbind.com/v1/connections" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
curl -X DELETE "https://api.agentflowbind.com/v1/connections/7d2e9f10-..." -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"DELETE returns 204; both endpoints currently share the webhooks:manage scope with run-event webhook subscriptions — an unrelated but coincidentally similarly-named concept — pending a dedicated connections:manage scope.